翻譯:瘋狂的技術宅原文:https://www.valentinog.com/bl...javascript
未經容許嚴禁轉載html
Svelte 是由 Rich Harris 建立的 JavaScript UI 庫。 Rich 認爲 virtual DOM 帶來了額外開銷,並提出了 Svelte,如今它正處於第三版的狀態。前端
可是你爲何要學習Svelte?而不是 React 或 Vue?嗯,它有一些有趣的賣點:java
在下面的教程中,我更關注 Svelte 3 的核心概念。react
無論怎樣,不要過度的去追逐潮流。 Svelte 3 確實頗有趣,雖然它在一些細節上還比較粗糙。你能夠經過本教程來試試 Svelte 3 的水到底有多深,並造成你本身的觀點。git
請慢慢享用。程序員
若是你對如下內容有基本的瞭解,那麼學習本教程就沒有問題:es6
若是你是前端初學者,那麼這個教程對你來講也許太過度了。可是不要絕望,先學習如下資源而後再回來。github
若是你須要學習 ES6模塊,請查看 JavaScript 中關於 import 和 export 語句的文檔。還有優秀的文章 ES6 Modules in depth。面試
要了解有關 Fetch API 的更多信息,請查看 Fetch API。
(是的,對於初學者來講,要學的東西是不少。但不是個人錯!)。
最後還要確保在系統上安裝了較新版本的 Node.js.
咱們不會在本教程中構建一個 「全棧的」 程序。相反,我將經過構建一些小的 UI 來引導你完成 Svelte 3 的核心概念。最後,你應該可以開始使用 Svelte 進行構建,並瞭解瞭如何建立組件以及如何處理事件等等。
如今享受學習 Svelte 的樂趣!
與全部現代 JavaScript 項目同樣,咱們須要完成設置項目全部必需的流程。若是要爲項目建立 Git 倉庫,請先完成這一步,而後在本地計算機上克隆倉庫。
克隆後,你應該已準備好使用 degit 建立一個新的 Svelte 項目了。不用擔憂,這不是另外一個須要學習的工具! Degit 是「愚蠢的」。它只是用來製做 Git repos 的副本,在咱們的例子中,咱們將把 Svelte 模板克隆到一個新文件夾中(或者在你的Git repo中)。
回顧一下,若是須要,能夠建立一個新的Git倉庫,而後在本地機器上克隆它:
git clone git@github.com:yourusername/svelte-tutorial.git
而後用 degit 在新文件夾中建立一個新的 Svelte 項目。若是文件夾不是空的,degit 會報錯,因此你須要加上強制標誌:
npx degit sveltejs/template svelte-tutorial --force
接下來進入新項目並安裝依賴項:
cd svelte-tutorial && npm i
如今你應該很高興的上路了!
項目就緒後,先來看看裏面都有些什麼。使用文本編輯器打開項目。你會看到一堆文件:
如今打開App.svelte並查看:
<script> export let name; </script> <style> h1 { color: purple; } </style> <h1>Hello {name}!</h1>
這是一個 Svelte 組件!真的,它須要的只是一個腳本標籤、一個樣式標籤和一些 HTML。 name 是一個變量,而後在 HTML 中的花括號之間插入並使用。如今不要過度關注 export 聲明,稍後會看到它的做用。
爲了開始探索 Svelte,咱們將當即開始用重火力進攻:先從 API 中獲取一些數據。
就此而言,Svelte 與 React 沒有什麼不一樣:它使用名爲 onMount 的方法。這是一個所謂的生命週期函數。很容易猜到 Svelte 從哪裏借用了這個想法:React 生命週期方法。
如今讓咱們在 src 文件夾中建立一個名爲 Fetch.svelte 的新 Svelte 組件。咱們的組件從 Svelte 導入 onMount 並向 API 發出獲取請求。 onMount 接受回調,並從該回調中發出請求。數據保存在 onMount 內名爲 data 的變量中:
<script> import { onMount } from "svelte"; let data = []; onMount(async function() { const response = await fetch("https://academy.valentinog.com/api/link/"); const json = await response.json(); data = json; }); </script>
如今打開 App.svelte 並導入新建立的組件(除了 script 標記,你能夠刪除全部內容):
<script> import Fetch from "./Fetch.svelte"; </script> <Fetch />
正如你所看到的,自定義組件的語法讓人想起 React 的 JSX。由於目前組件只是進行 API 調用,還不會顯示任何內容。接下來讓咱們添加更多東西。
在 React 中,咱們已經習慣了建立元素列表的映射功能。在 Svelte 中有一個名爲「each」的塊,咱們要用它來建立一個連接列表。 API 返回一個對象數組,每一個對象都有一個標題和一個 url。如今要添加一個「each」塊:
<script> import { onMount } from "svelte"; let data = []; onMount(async function() { const response = await fetch("https://academy.valentinog.com/api/link/"); const json = await response.json(); data = json; }); </script> {#each data as link} // do stuff // {/each}
注意「each」是如何生成變量 data 的,我將每一個元素提取爲 「link」。要生成元素列表,只需確保將每一個元素包裝在一個 ul 元素中:
<script> import { onMount } from "svelte"; let data = []; onMount(async function() { const response = await fetch("https://academy.valentinog.com/api/link/"); const json = await response.json(); data = json; }); </script> <ul> {#each data as link} <li> <a href={link.url}>{link.title}</a> </li> {/each} </ul>
如今轉到你的終端,進入項目文件夾並運行:
npm run dev
訪問 http://localhost:5000/ ,你應該看到一個連接列表:
很好!你學會了如何在 Svelte 中生成元素列表。接下來讓咱們的組件能夠重複使用。
重用UI組件的能力是這些現代 JavaScript 庫的「存在理由」。例如在 React 中有 props、自定義屬性(甚至函數或其餘組件),咱們能夠把它們傳遞給本身的組件,使它們更靈活。
如今 Fetch.svelte 不是可重用的,由於 url 是硬編碼的。但沒必要擔憂,Svelte 組件也能夠從外面接收props。讓首先將 url 變爲一個變量(我將向你展現組件的相關部分):
<script> import { onMount } from "svelte"; let url = "https://academy.valentinog.com/api/link/"; let data = []; onMount(async function() { const response = await fetch(url); const json = await response.json(); data = json; }); </script>
有一個技巧可使 url 成爲 props:只需在變量前加上 export:
<script> import { onMount } from "svelte"; // export the variable to make a prop export let url = "https://academy.valentinog.com/api/link/"; let data = []; onMount(async function() { const response = await fetch(url); const json = await response.json(); data = json; }); </script>
如今打開 App.svelte 並經過傳遞 url prop 來更新 Fetch 組件:
<script> import Fetch from "./Fetch.svelte"; </script> <Fetch url="https://jsonplaceholder.typicode.com/todos" />
如今,你的組件調用的是新端點而不是默認 URL。另外一個好處是標記爲 props 的變量可能具備默認值。在咱們的例子中,「https://academy.valentinog.co...」是默認 props,做爲沒有 props 傳遞時的後備。
如今看看當咱們須要不止一個 props 時會發生什麼。
固然,Svelte 組件可能有多個 props。讓咱們爲組件添加另外一個名爲 title 的 props:
<script> import { onMount } from "svelte"; export let url = "https://academy.valentinog.com/api/link/"; export let title = "A list of links"; let data = []; onMount(async function() { const response = await fetch(url); const json = await response.json(); data = json; }); </script> <h1>{title}</h1> <ul> {#each data as link} <li> <a href={link.url}>{link.title}</a> </li> {/each} </ul>
再次從 App.svelte 傳遞新的 props:
<script> import Fetch from "./Fetch.svelte"; </script> <Fetch url="https://jsonplaceholder.typicode.com/todos" title="A list of todos" />
當 props 開始增多時,你會發現上述方法不切實際。幸運的是,有一種方法能夠傳播 props。將 props 聲明爲對象並將它們分佈在組件上:
<script> import Fetch from "./Fetch.svelte"; const props = { url: "https://jsonplaceholder.typicode.com/todos", title: "A list of todos" }; </script> <Fetch {...props} />
很不錯不是嗎?但我仍然不滿意。我想讓 Fetch 組件更加可重用,該怎麼辦?
Fetch 這個命名對於組件來講並不差勁,若是它是一個 HTML 列表的話。有一種方法能夠從外面傳遞該列表,就像React 中的子 props 同樣。在 Svelte,咱們將子組件稱爲插槽(slot)。
第一步,我將從 Fetch.svelte 中刪除全部標記,將其替換爲插槽,使它擺脫 prop 的「title」:
<script> import { onMount } from "svelte"; export let url = "https://academy.valentinog.com/api/link/"; let data = []; onMount(async function() { const response = await fetch(url); const json = await response.json(); data = json; }); </script> <slot />
接下來,能夠將子元素從外部傳遞給 Fetch,這就發生在 App.svelte 中:
<script> import Fetch from "./Fetch.svelte"; const props = { url: "https://jsonplaceholder.typicode.com/todos" }; </script> <Fetch {...props}> <h1>A list of todos</h1> <ul> <li>now what?</li> </ul> </Fetch>
但如今咱們遇到了問題。我須要data,它存在於 Fetch.svelte 中,這點很重要,由於我不想手動去建立列表。
在 React 中你能夠找到一個 HOC、渲染 props 或 hooks。換句話說,我想渲染一個子組件,可是子組件應該從父組件獲取 data。
在 Svelte 中,你能夠經過將值反向傳遞給父組件來得到相同的結果。首先將 data 做爲 prop 傳遞給你的插槽:
<script> import { onMount } from "svelte"; export let url = "https://academy.valentinog.com/api/link/"; export let title = "A list of links"; let data = []; onMount(async function() { const response = await fetch(url); const json = await response.json(); data = json; }); </script> <!-- {data} is a shortand for data={data} --> <slot {data} />
從外面你可使用符號 let:data={data}
訪問數據,這裏簡寫爲 let:data
:
<script> import Fetch from "./Fetch.svelte"; const props = { url: "https://jsonplaceholder.typicode.com/todos" }; </script> <!-- let:data is like forwarding a component's data one level upward --> <Fetch {...props} let:data> <h1>A list of todos</h1> <ul> {#each data as link} <li>{link.title}</li> {/each} </ul> </Fetch>
如今可使用來自 Fetch 組件的數據了,它可用於個人每一個塊。這就像將組件的內部數據向上轉發一級。
雖然起初多是反直覺的,但這彷佛是一種簡潔的方法。你怎麼看?在下一節中,咱們將介紹 Svelte 中的事件處理。
咱們將構建一個表單組件來講明 Svelte 如何處理事件。建立一個名爲 Form.svelte 的新文件。如今它包含用於搜索的 input 和提交類型的 button:
<script> </script> <form> <label for="search">Search:</label> <input type="search" id="search" required /> <button type="submit">Search</button> </form>
(做爲練習,你能夠將每一個元素提取到其本身的組件中)。
而後在 App.svelte 中包含新組件:
<script> import Form from "./Form.svelte"; </script> <Form />
現用程序應該能夠在瀏覽器中渲染你的表單了。此時若是你嘗試提交表單,默認行爲是:瀏覽器觸發刷新。
要控制 「vanilla」 中的表單,我會爲 submit 事件註冊一個事件監聽器。而後在處理 handler 內部阻止使用 event.preventDefault()
的默認值:
// vanilla JS example var form = document.getElementsByTagName('form')[0] form.addEventListener('submit', function(event){ event.preventDefault(); });
在 Svelte 組件內部狀況有所不一樣:使用「on」註冊事件handler,後面分別使用事件名稱和處理函數:
<script> function handleSubmit(event) { // do stuff } </script> <form on:submit={handleSubmit}> <label for="search">Search:</label> <input type="search" id="search" required /> <button type="submit">Search</button> </form>
此外在 Svelte 中有事件修飾符。其中最重要的是:
能夠在事件名稱以後使用修飾符 preventDefault 來停用表單上的默認
<script> function handleSubmit(event) { // do stuff } </script> <form on:submit|preventDefault={handleSubmit}> <label for="search">Search:</label> <input type="search" id="search" required /> <button type="submit">Search</button> </form>
還能夠將 handleSubmit 做爲 prop 來傳遞,以便使組件更加靈活。這是一個例子:
<script> export let handleSubmit = function(event) { // default prop }; </script> <form on:submit|preventDefault={handleSubmit}> <label for="search">Search:</label> <input type="search" id="search" required /> <button type="submit">Search</button> </form>
而已。如今把這個簡單的程序更進一步:我想過濾連接列表。表單已經到位但咱們須要將 Fetch.svelte 與 Form.svelte 鏈接起來。咱們開始作吧!
讓咱們回顧一下到目前爲止所作的事情。咱們有兩個組件,Fetch.svelte:
<script> import { onMount } from "svelte"; export let url = "https://academy.valentinog.com/api/link/"; let data = []; onMount(async function() { const response = await fetch(url); const json = await response.json(); data = json; }); </script> <slot {data} />
和 Form.svelte:
<script> export let handleSubmit = function(event) { // default prop }; </script> <form on:submit|preventDefault={handleSubmit}> <label for="search">Search:</label> <input type="search" id="search" required /> <button type="submit">Search</button> </form>
App.svelte 是根組件。爲方便起見,讓咱們在 App 中渲染 Form 和 Fetch:
<script> import Fetch from "./Fetch.svelte"; import Form from "./Form.svelte"; </script> <Form /> <Fetch let:data> <h1>A list of links</h1> <ul> {#each data as link} <li> <a href={link.url}>{link.title}</a> </li> {/each} </ul> </Fetch>
Fetch.svelte 從 API 獲取數據並向上轉發數據。所以當使用塊做爲插槽時,能夠將數據傳遞給它的子節點。
如今我但願用戶根據他在表單中輸入的搜索詞來過濾數據。看起來像 Form 和 Fetch 須要溝通。讓咱們看看如何實現這一點。
咱們須要一個搜索項來過濾數據數組。搜索詞能夠是從外部傳遞給 Fetch.svelte 的 props。打開 Fetch.svelte 並添加新的 prop searchTerm:
<script> import { onMount } from "svelte"; export let url = "https://academy.valentinog.com/api/link/"; // new prop export let searchTerm = undefined; let data = []; onMount(async function() { const response = await fetch(url); const json = await response.json(); data = json; }); </script> <slot {data} />
(searchTerm 被指定爲 undefined,以防止 Svelte 對我抱怨 「Fetch 在建立時找不到預期的 prop searchTerm」)。
接下來須要一個新變量來保存 json 響應,由於咱們將根據 searchTerm 過濾該響應。添加一個名爲 jsonResponse 的新變量,使用 jsonResponse 來存儲 API 的響應而不是將 json 保存到數據:
<script> import { onMount } from "svelte"; export let url = "https://academy.valentinog.com/api/link/"; // new prop export let searchTerm; // new variable let jsonResponse = []; let data = []; onMount(async function() { const response = await fetch(url); const json = await response.json(); // save the response in the new variable jsonResponse = json; }); </script> <slot {data} />
此時變量數據將包含:
對於過濾數組元素,咱們能夠基於 RegExp 對照標題屬性進行匹配。 (API返回一個對象數組。每一個對象都有 title 和 url)。第一個實現多是:
const regex = new RegExp(searchTerm, "gi"); const data = searchTerm ? jsonResponse.filter(element => element.title.match(regex)) : jsonResponse;
說得通!讓咱們看看完整的組件:
<script> import { onMount } from "svelte"; export let url = "https://academy.valentinog.com/api/link/"; // new prop export let searchTerm = undefined; // new variable let jsonResponse = []; const regex = new RegExp(searchTerm, "gi"); const data = searchTerm ? jsonResponse.filter(element => element.title.match(regex)) : jsonResponse; onMount(async function() { const response = await fetch(url); const json = await response.json(); // save the response in the new variable jsonResponse = json; }); </script> <slot {data} />
在這一點上,咱們須要對 App.svelte 進行一些調整。 searchTerm 應該是來自外部的動態 props。而後咱們在用戶提交表單時攔截輸入的值。打開 App.svelte 並將 searchTerm 做爲 Fetch 的 prop 傳遞:
<script> import Fetch from "./Fetch.svelte"; import Form from "./Form.svelte"; let searchTerm; </script> <Form /> <Fetch {searchTerm} let:data> <h1>A list of links</h1> <ul> {#each data as link} <li> <a href={link.url}>{link.title}</a> </li> {/each} </ul> </Fetch>
接下來咱們建立並傳遞 handleSubmit 做爲 Form 的 prop,並在 App.svelte 內部保存用戶在變量 searchTerm 中輸入的搜索詞:
<script> import Fetch from "./Fetch.svelte"; import Form from "./Form.svelte"; let searchTerm; function handleSubmit() { const { value } = this.elements.search; searchTerm = value; } </script> <Form {handleSubmit} /> <Fetch {searchTerm} let:data> <h1>A list of links</h1> <ul> {#each data as link} <li> <a href={link.url}>{link.title}</a> </li> {/each} </ul> </Fetch>
幾乎完成了。保存全部文件並運行開發服務器。你會看到......一個空白的頁面!
這是怎麼回事?趕快進入下一節!
Svelte 處理計算值的方式可能一開始看起來不直觀。咱們的問題在於 Fetch.svelte,它來自如下幾行:
const regex = new RegExp(searchTerm, "gi"); const data = searchTerm ? jsonResponse.filter(element => element.title.match(regex)) : jsonResponse;
思考一下,假設咱們有兩個值, regex 取決於 searchTerm,咱們但願每次後者更改時要從新生成前者。
而後咱們有數據:它應該每次從新處理 searchTerm 和正則表達式。就像電子表格同樣:一個值可能取決於其餘值。
Svelte 從「反應式編程」中汲取靈感,並對所謂的計算值使用奇怪的語法。這些值在 Svelte 3 中被稱爲「反應聲明」。下面是應該如何調整上述代碼:
$: regex = new RegExp(searchTerm, "gi"); $: data = searchTerm ? jsonResponse.filter(element => element.title.match(regex)) : jsonResponse;
$:不是外來的語法。它只是簡單的 JavaScript,它被稱爲標籤聲明。
這裏是完整的 Fetch.svelte:
<script> import { onMount } from "svelte"; export let url = "https://academy.valentinog.com/api/link/"; export let searchTerm = undefined; let jsonResponse = []; $: regex = new RegExp(searchTerm, "gi"); $: data = searchTerm ? jsonResponse.filter(element => element.title.match(regex)) : jsonResponse; onMount(async function() { const response = await fetch(url); const json = await response.json(); jsonResponse = json; }); </script> <slot {data} />
如今,搜索功能將像魔法同樣工做:
(過濾 API 級別的連接比每次獲取全部連接更好)。
若是你想知道如何用 React實現相同的「app」,請看下一部分。
用 React 構建的相同功能的 demo 看起來是怎樣的呢?這是 App.js,至關於 App.svelte:
import React, { useState } from "react"; import Fetch from "./Fetch"; import Form from "./Form"; function App() { const [searchTerm, setSearchTerm] = useState(""); const fetchProps = { url: "https://academy.valentinog.com/api/link/", searchTerm }; function handleSubmit(event) { event.preventDefault(); const { value } = event.target.elements.search; setSearchTerm(value); } return ( <> <Form handleSubmit={handleSubmit} /> <Fetch {...fetchProps} render={links => { return ( <> <h1>A list of links</h1> <ul> {links.map(link => ( <li key={link.url}> <a href={link.url}>{link.title}</a> </li> ))} </ul> </> ); }} /> </> ); } export default App;
這裏我使用帶有渲染 props 的 Fetch 組件。我可使用 hook,但我想告訴你一樣的概念如何適用於 Svelte 和React。
換一種說法:
若是你將 App.js 與 Svelte 對應代碼(點擊這裏)進行比較,能夠看到典型的 Svelte 組件比 React 等效組件更加簡潔。
經過在 Svelte 3 中的事實很容易解釋,不須要顯式調用 setSomeState 或相似的函數。 僅經過爲變量賦值,Svelte 就能「作出反應」。
接下來是 Form.js,Form.svelte 的 React 實現:
import React from "react"; function Form(props) { return ( <form onSubmit={props.handleSubmit}> <label htmlFor="search">Search:</label> <input type="search" id="search" required={true} /> <button type="submit">Search</button> </form> ); } export default Form;
沒有什麼可看的,只是一個函數接受一些 props。
最後是 Fetch.js,複製 Fetch.svelte 的功能:
import { useState, useEffect } from "react"; function Fetch(props) { const { url, searchTerm } = props; const [links, setLinks] = useState([]); const regex = new RegExp(searchTerm, "gi"); const data = searchTerm ? links.filter(link => link.title.match(regex)) : links; useEffect(() => { fetch(url) .then(response => response.json()) .then(json => setLinks(json)); }, [url]); return props.render(data); } Fetch.defaultProps = { url: "https://academy.valentinog.com/api/link/" }; export default Fetch;
上面的組件使用 hook 和渲染 props:再次強調這是沒必要要的,由於你能夠提取 自定義 hook。這裏是 Fetch.svelte:
<script> import { onMount } from "svelte"; export let url = "fillThis"; export let searchTerm = undefined; let jsonResponse = []; $: regex = new RegExp(searchTerm, "gi"); $: data = searchTerm ? jsonResponse.filter(element => element.title.match(regex)) : jsonResponse; onMount(async function() { const response = await fetch(url); const json = await response.json(); jsonResponse = json; }); </script> <slot {data} />
他們看起來和我同樣帥😀。然而,這些例子遠遠達不到一個真正的大程序的地步。
我被問到與 React 和 Vue 相比,對 Svelte 的見解是什麼?我不能評價 Vue,由於我沒有太多的使用經驗,但我能夠看到 Svelte 如何向其借鑑的。
說到 React,Svelte 對我來講很合理,看起來更直觀。在粗略的一瞥中,Svelte 3 彷佛只是另外一種作事方式,也許比 React 更聰明。
在 Svelte 中真正吸引人的是,它與 React 和 Vue 不一樣,沒有 virtual DOM。換句話說,庫和實際的文檔對象模型之間沒有抽象:Svelte 3 可被編譯爲可能的最小原生 JavaScript。若是你在受限制的環境中運行程序,這將很是有用。
回顧一下,Svelte 是一個很是有趣的庫,但至少在文檔、生態系統和工具將逐漸成熟以前我會給它更多的時間。
爲了解更多關於 Svelte 的信息,我不能只推薦官方文檔和例子。
本教程的源代碼在這裏。
另外請務必去看一看 Svelte 做者的演講:https://www.youtube.com/embed...
還能作些什麼?若是你願意,Svelte 3 還有不少要學的東西。開箱即用的好東西太多了:
說再見以前,我還要再囉嗦幾句。
JavaScript是殘酷的。各類庫來去匆匆,總會有新的東西須要學習。多年來,我學會了不要過於依賴任何特定的 JavaScript 庫,但說實話,我真的很喜歡 React 和 Redux。
React 爲你們帶來了「組件」,另外一方面,庫自己須要具備高度專業化的知識才能掌握。相比之下,Vue 更適合初學者,但不幸的是它並不像 React 那樣被視爲「時尚」(不管那意味着什麼)。
Svelte 3 充分利用了兩個世界:Svelte 組件看起來像 Vue,而 React 的一些概念也一樣適用。
Svelte 比 React 更直觀,特別是當一個初學者在 hook 時代去接觸 React 時。固然,React 不會很快消失,但我很期待看到 Svelte 的將來。
最後我仍然要老生常談:要持續不斷的學習。