- 原文地址:Svelte for the Experienced React Dev
- 原文做者:Adam Rackis
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:沒事兒
- 校對者:liyaxuanliyaxuan、CarlosChenN、霜羽 Hoarfroster
這篇文章將從富有 React 開發經驗的開發者的角度快速的介紹 Sevlte。首先我會作一個概覽,而後重點關注 state 管理和 DOM 交互能力等等。我打算把進度加快一點,這樣就能覆蓋更多的話題。總之,但願能引發你對 Svelte 的興趣。css
關於對 Svelte 的介紹,沒有任何博客能夠和官方教程和文檔相比。html
讓咱們先快速瀏覽一遍 Svelte 的組件風格前端
<script>
let number = 0;
</script>
<style>
h1 {
color: blue;
}
</style>
<h1>Value: {number}</h1>
<button on:click={() => number++}>Increment</button>
<button on:click={() => number--}>Decrement</button>
複製代碼
這個內容被存放在 .svelte
文件中,經過 Rollup 或 webpack 插件加工後生成 Svelte 組件。咱們能夠經過一些小片斷了解。node
首先,咱們添加一個 <script>
標籤存放全部咱們須要的 state。react
咱們也能夠添加一個 <style>
標籤存放全部咱們須要的 CSS。這些樣式 只做用於這個組件,因此 <h1>
元素在這個 組件中將會是藍色的。是的,被限制做用域的樣式內置於 Svelte,不須要外部依賴。在 React 中,想要達到這樣受限制的樣式,你須要使用第三方插件相似 css-modules, styled-components, 或者其餘的 (有幾十種,甚至上百種選擇).android
接下來是一些 html 標記,像你預期的,你將須要學習相似 {#if}
、{#each}
等 html 捆綁方法。相較於在 React 中,一切皆 JavaScript 的概念而言,這類特殊領域的語言功能可能看上去像是一個退步。但有幾件事值得注意,Svelte 容許你在這些捆綁中放入任意的 JavaScript 代碼。因此相似下面這類代碼是徹底有效的。webpack
{#if childSubjects?.length}
複製代碼
若是你以前是使用 Knockout 或者 Ember,但如今使用而且忠於 React,那麼這可能會令你感到驚喜。ios
還有,Svelte 處理組件的方法和 React 徹底不一樣。只要一個組件的狀態或者父組件中的任何地方(除非你緩存了)發生了改變,React 會從新運行全部的組件。這可能會致使效率低下,這也是爲何 React 會使用 useCallback
和 useMemo
來防止額外的從新計算數據。git
在另外一方面,Svelte 會分析你的模板,而且在相關的狀態改變時建立目標 DOM 的更新代碼。在上面的組件中,Svelte 將會看到 number
在哪裏改變,而後在變動完成後添加代碼去更新 <h1>
的內容,這表示你不須要擔憂函數或者對象的緩存。事實上,你甚至不須要擔憂反作用的依賴列表,咱們稍後會討論這個問題。github
可是咱們先談論……
在 React 中,當咱們須要管理 state 的時候,咱們使用 useState
hook。咱們向它提供一個初始值,而後獲得一個包含當前值和用於設置新值的函數的元組。看起來多是這樣:
import React, { useState } from "react";
export default function (props) {
const [number, setNumber] = useState(0);
return (
<> <h1>Value: {number}</h1> <button onClick={() => setNumber(n => n + 1)}>Increment</button> <button onClick={() => setNumber(n => n - 1)}>Decrement</button> </>
);
}
複製代碼
setNumber
函數能夠傳遞到任何地方,好比子組件等。
這個在 Svelte 中會簡單點。咱們能夠建立一個變量,在須要時更新它。 Svelte 的提早編譯(和 React 即時更新不一樣)將會追蹤變量更新的腳步,而後推進 DOM 更新。和上面同樣簡單的例子:
<script>
let number = 0;
</script>
<h1>Value: {number}</h1>
<button on:click={() => number++}>Increment</button>
<button on:click={() => number--}>Decrement</button>
複製代碼
另外一個須要注意的是,Svelte 不須要 JSX 那樣的單獨的包裹元素,也沒有 React 片斷語法 <></>
的等價物。
但若是咱們想要傳遞一個更新函數給子組件呢?使它能更新這塊的狀態,就像咱們用 React 作的那樣,咱們能夠寫一個更新函數:
<script>
import Component3a from "./Component3a.svelte";
let number = 0;
const setNumber = cb => number = cb(number);
</script>
<h1>Value: {number}</h1>
<button on:click={() => setNumber(val => val + 1)}>Increment</button>
<button on:click={() => setNumber(val => val - 1)}>Decrement</button>
複製代碼
如今,咱們能夠把這個更新函數傳遞到任何咱們須要的地方,或者繼續期待一個更自動化的解決方案。
React 還有 useReducer
hook,讓咱們能夠塑造更復雜的狀態。咱們提供一個 reducer 函數,而後獲得咱們當前的值,以及一個 dispatch 函數讓咱們能夠用一個給定的參數去調用 reducer,從而觸發一個狀態更新,無論 reducer 返回的是什麼。咱們上面的計數器例子可能看起來會是這樣:
import React, { useReducer } from "react";
function reducer(currentValue, action) {
switch (action) {
case "INC":
return currentValue + 1;
case "DEC":
return currentValue - 1;
}
}
export default function (props) {
const [number, dispatch] = useReducer(reducer, 0);
return (
<div> <h1>Value: {number}</h1> <button onClick={() => dispatch("INC")}>Increment</button> <button onClick={() => dispatch("DEC")}>Decrement</button> </div>
);
}
複製代碼
Svelte 沒有相似的功能,可是它有一個稱爲 store 的模塊。最簡單的是 writable store,是持一個值的 object。想要設置一個新值,你能夠調用 store 上的 set
方法並傳遞一個新值,或者調用 update,傳入一個回調函數,這個函數接受當前值而且返回新值(和 React 的 useState
同樣).
在須要時讀取 store 的當前值,能夠調用get
函數,它會返回當前值。Store 也有一個 subscribe 函數,咱們能夠傳入一個回調函數,在值改變時被執行。
Svelte 是簡潔輕量的,其中有一些不錯的語法快捷方式。若是你在一個組件內部,你能夠給 store 加一個 $ 前綴用於讀取其值,或者經過直接賦值去更新值。這是上面的計數器例子,使用了一個 store,以及一些額外的反作用日誌打印用於展現 subscribe 是如何工做的:
<script>
import { writable, derived } from "svelte/store";
let writableStore = writable(0);
let doubleValue = derived(writableStore, $val => $val * 2);
writableStore.subscribe(val => console.log("current value", val));
doubleValue.subscribe(val => console.log("double value", val))
</script>
<h1>Value: {$writableStore}</h1>
<!-- manually use update -->
<button on:click={() => writableStore.update(val => val + 1)}>Increment</button>
<!-- use the $ shortcut -->
<button on:click={() => $writableStore--}>Decrement</button>
<br />
Double the value is {$doubleValue}
複製代碼
注意,我在上面添加了一個 derived
store。文檔深刻的介紹了這個,但簡單來講,derived
store 讓你可使用和 writable store 同樣的語法,讓一個 store (或許多 store) 映射出一個新值。
Svelte 中的 Store 很是靈活。咱們能夠將多個 store 傳遞到子組件中,更改、組合它們,甚至經過傳遞一個 derived store 使它們只讀。若是咱們要把一些 React 的代碼轉化爲 Svelte,咱們甚至能夠重建一些你可能喜歡或須要的 React 抽象。
說完這些,讓咱們回到以前 React 的 useReducer
hook 上。
咱們的確是真的喜歡經過定義 reducer 函數來維護和更新 state。讓咱們看看使用 Svelte 的 store 去模仿 React 的 useReducer
會有多難。咱們想要調用咱們本身的 useReducer
,傳入一個帶有初始值的 reducer 函數,而後獲得帶有當前值的 store,這和 dispatch 函數調用 reducer 去更新 store 是同樣的,完成這個任務實際上不算太糟糕。
export function useReducer(reducer, initialState) {
const state = writable(initialState);
const dispatch = (action) =>
state.update(currentState => reducer(currentState, action));
const readableState = derived(state, ($state) => $state);
return [readableState, dispatch];
}
複製代碼
Svelte 的用法和 React 幾乎是同樣的。惟一的區別是咱們當前的值是一個 store,而不是一個原始值,因此咱們須要加上一個 $
前綴來讀取值(或者手動調用 store 上的 get
或 subscribe
)。
<script>
import { useReducer } from "./useReducer";
function reducer(currentValue, action) {
switch (action) {
case "INC":
return currentValue + 1;
case "DEC":
return currentValue - 1;
}
}
const [number, dispatch] = useReducer(reducer, 0);
</script>
<h1>Value: {$number}</h1>
<button on:click={() => dispatch("INC")}>Increment</button>
<button on:click={() => dispatch("DEC")}>Decrement</button>
複製代碼
useState
呢?若是你真的喜歡 React 的 useState
hook,實現也很簡單。實際上,我並無以爲這是一個頗有用的抽象,但這是個有趣的練習,能夠展現 Svelte 的靈活性。
export function useState(initialState) {
const state = writable(initialState);
const update = (val) =>
state.update(currentState =>
typeof val === "function" ? val(currentState) : val
);
const readableState = derived(state, $state => $state);
return [readableState, update];
}
複製代碼
在結束 state 管理這部分以前,我要說起最後一個對 Svelte 而言比較特殊的技巧。咱們已經知道了 Svelte 容許咱們使用任何咱們能用的 Rect 方法來傳遞更新函數到組件樹。容許子組件通知他們的父組件,state 變化,這是個頻繁的操做,咱們已經作了幾千次。一個子組件改變了 state,而後調用一個父組件傳遞過來的函數,這樣父組件就能夠接收 state 改變。
除了支持回調函數的傳遞,Svelte 也容許父組件與子組件 state 的雙向綁定。好比,咱們有這樣一個組件:
<!-- Child.svelte -->
<script>
export let val = 0;
</script>
<button on:click={() => val++}>
Increment
</button>
Child: {val}
複製代碼
上面的例子建立一個帶有 val
屬性的組件。在 Svelte 中,export
關鍵字用於組件聲明 props。一般,咱們會把 props 傳入到一個組件中,但這裏有點不一樣。好比上面的例子,val
prop 被子組件修改了。在 React 中,這是錯誤的,可能會引起 bug,但在 Svelte 中,渲染這個組件的組件能夠作這個。
<!-- Parent.svelte -->
<script>
import Child from "./Child.svelte";
let parentVal;
</script>
<Child bind:val={parentVal} />
Parent Val: {parentVal}
複製代碼
這裏,在父組件中咱們爲子組件的 val
prop 從新綁定了一個變量。若是子組件的 val
prop 變化,那麼父組件的 parentVal
也會自動被 Svelte 更新。
雙向綁定是存在一些爭論。若是你不喜歡,那就不要用它。可是少許使用,我認爲這會是一個很是方便的工具,能夠減小模板。
在 React 中,咱們使用 useEffect
hook 管理反作用。像這樣:
useEffect(() => {
console.log("Current value of number", number);
}, [number]);
複製代碼
咱們寫了一個函數,以及依賴列表。每一次渲染,React 都會檢查列表中的每個元素,若是有一個與上一次渲染時不一樣,那麼這個回調函數就會再次運行。若是咱們想要在上一次運行後運行一個 cleanup 函數,那麼咱們能夠從 effect 中返回一個 cleanup 函數。
像數字變化這類簡單的需求,這很簡單。可是任何有經驗的 React 開發者都知道,對於非瑣碎的使用案例,useEffect
會是個潛在的麻煩。這很是容易,在依賴列表遺漏一些什麼從而引起過期的閉包問題。
在 Svelte 中,操做反作用最基礎的形式是反應性的聲明。看起來像這樣。
$: {
console.log("number changed", number);
}
複製代碼
給一個代碼塊加上一個前綴 $:
,而後放入咱們想要執行的代碼。Svelte 分析哪一個依賴被讀,只要它們改變,Svelte 會從新運行這個代碼塊。沒有直接的方法能夠在上一次這個反應性代碼塊運行後去運行 cleanup,若是真的須要能夠作一個替代方法,這很是簡單。
let cleanup;
$: {
cleanup?.();
console.log("number changed", number);
cleanup = () => console.log("cleanup from number change");
}
複製代碼
這並不會致使無限循環:在一個反應性的代碼塊內從新賦值不會再引起這個代碼塊運行。
這是有效的,cleanup effects 一般會用在組件卸載時,Svelte 對此有一個內置功能 onMount
函數,使咱們能夠返回一個 cleanup 函數可以在組件銷燬時執行,更直接還有一個onDestroy
函數,能夠作咱們想作的事。
以上的一切都很好用,但 action 纔是 Svelte 的最大亮點。反作用頻繁的捆綁 DOM 節點。咱們可能想在一個 DOM 節點上集成一個老式的(但仍然很不錯) jQuery 插件,而後在節點離開 DOM 的時候拆除它;或者咱們想爲一個節點設置一個 ResizeObserver
,而後在節點離開 DOM 的時候分離它,等等。這是很是普通的需求,Svelte 將其內置在 action 中。讓咱們一塊兒去看看。
{#if show}
<div use:myAction>
Hello
</div>
{/if}
複製代碼
注意這個語法 use:actionName
。這裏咱們將 <div>
與一個稱做 myAction
的 action 捆綁,這個 action 只是個函數。
function myAction(node) {
console.log("Node added", node);
}
複製代碼
只要 <div>
進入 DOM,就會調用這個 action,而且傳遞這個 DOM 節點給 action。這是一個時機能夠去添加 jQuery 插件以及設置 ResizeObserver
等等。不僅這樣,咱們還能夠從中返回一個 cleanup 函數,好比這樣:
function myAction(node) {
console.log("Node added", node);
return {
destroy() {
console.log("Destroyed");
}
};
}
複製代碼
當節點離開 DOM 的時候,destroy()
將會執行,這是咱們銷燬 jQuery 插件的地方。
咱們還能夠傳遞參數給 action, 像這樣:
<div use:myAction={number}>
Hello
</div>
複製代碼
這個參數將做爲 action 函數的第二個參數。
function myAction(node, param) {
console.log("Node added", node, param);
return {
destroy() {
console.log("Destroyed");
}
};
}
複製代碼
若是你想在參數變化時作額外的工做,你能夠返回一個 update 函數。
function myAction(node, param) {
console.log("Node added", node, param);
return {
update(param) {
console.log("Update", param);
},
destroy() {
console.log("Destroyed");
}
};
}
複製代碼
當傳遞給 action 的參數變化時,update 函數將會運行。向一個 action 傳遞多個參數,咱們能夠傳遞一個 object。
<div use:myAction={{number, otherValue}}>
Hello
</div>
複製代碼
只要 object 的屬性改變,Svelte 就會再次運行 update 函數。
Actions 是我最喜歡的 Svelte 功能之一,它們很是強大。
Svelte 還有不少其餘的,React 沒有與之相對的功能。還有不少表單捆綁(教程有涵蓋),以及 CSS 輔助。
來自 React 的開發者可能會驚喜,Svelte 開箱即用的動畫。與其在 npm 裏搜索而後但願能找到最好的,不如…內置。它甚至包含了彈性動畫,進入離開的動畫,Svelte 稱之爲transitions。
對於 React.Chidren
,Svelte 與之對應的是 slots,Svelte 的文檔很好的講解了這個。我發現它們比 React’s Children API 更簡單些。
最後,我最喜歡的功能之一,幾乎算是隱藏的功能,經過 svelte:options
的屬性tagName
,Svelte 能夠將本身的組件編譯爲真實的 web 組件。但必定要在 webpack 或 Rollup 配置中設置對應的屬性。在 webpack 中是這樣的:
{
loader: "svelte-loader",
options: {
customElement: true
}
}
複製代碼
這篇文章中的任何一個知識點均可以單獨擰出來寫一個 blog 了,好比 state 管理和 actions,而咱們可能只瞭解到了一些皮毛,咱們看到了 Svelte 的功能,不只是與 React 相匹配,甚至能夠模仿不少 React 的 API。而後以前咱們簡單的談到了一些 Svelte 的便利,好比內置動畫(或者過渡)以及將 Svelte 組件轉化爲真實的 web 組件。
我但願我成功的激起了你的興趣,有不少文章,教程或者在線課程等等能夠更深刻探究。若是你有任何問題能夠在評論區告訴我。
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。