Solid.js 就是我理想中的 React
我大約在三年前開始在工作中使用 React。巧合的是,當時正好是 React Hooks 出來的時候。我當時的專案程式碼庫有很多類元件,總讓我覺得很笨重。
我們來看看下面的例子:一個每秒遞增一次的計數器。
<code data-type="codeline">class Counter extends React.Component {</code><code data-type="codeline"> constructor() {</code><code data-type="codeline"> super();</code><code data-type="codeline"> this.state = { count: 0 };</code><code data-type="codeline"> this.increment = this.increment.bind(this);</code><code data-type="codeline"> }</code><code data-type="codeline"> increment() {</code><code data-type="codeline"> this.setState({ count: this.state.count + 1 });</code><code data-type="codeline"> }</code><code data-type="codeline"> componentDidMount() {</code><code data-type="codeline"> setInterval(() => {</code><code data-type="codeline"> this.increment();</code><code data-type="codeline"> }, 1000);</code><code data-type="codeline"> }</code><code data-type="codeline"> render() {</code><code data-type="codeline"> return <div>The count is: {this.state.count}</div>;</code><code data-type="codeline"> }</code><code data-type="codeline">}</code>
複製程式碼
對於一個自動遞增的計數器來說要寫這麼多程式碼可不算少。更多的模板和儀式意味著出錯的可能性更大,開發體驗也更差。
Hooks 很漂亮,但是容易出錯
當 hooks 出現的時候我非常興奮。我的計數器可以簡化為以下寫法:
<code data-type="codeline">function Counter() {</code><code data-type="codeline"> const [count, setCount] = useState(0);</code><code data-type="codeline"> useEffect(() => {</code><code data-type="codeline"> setInterval(() => {</code><code data-type="codeline"> setCount(count + 1);</code><code data-type="codeline"> }, 1000);</code><code data-type="codeline"> }, []);</code><code data-type="codeline"> return <div>The count is: {count}</div>;</code><code data-type="codeline">}</code>
複製程式碼
等等,這其實是不對的。我們的 useEffect hook 在 count 周圍有一個陳舊閉包,因為我們沒有把 count 包含在 useEffect 依賴陣列中。從依賴陣列中省略變數是 React hooks 的一個常見錯誤,如果你忘記了,有一些 linting 規則會警告你的。
我稍後會回到這個問題上。現在,我們把缺少的 count 變數新增到依賴陣列中:
<code data-type="codeline">function Counter() {</code><code data-type="codeline"> const [count, setCount] = useState(0);</code><code data-type="codeline"> useEffect(() => {</code><code data-type="codeline"> setInterval(() => {</code><code data-type="codeline"> setCount(count + 1);</code><code data-type="codeline"> }, 1000);</code><code data-type="codeline"> }, [count]);</code><code data-type="codeline"> return <div>The count is: {count}</div>;</code><code data-type="codeline">}</code>
複製程式碼
但現在我們遇到了另一個問題,看看應用程式的執行效果:

精通 React 的人們可能知道發生了什麼事情,因為你每天都在與這種問題作鬥爭:我們建立了太多的間隔(每次重新執行效果時都會建立一個新間隔,也就是每次我們增加 count 時間隔都會增加)。可以通過幾種方式來解決這個問題:
-
從清除間隔的 useEffect hook 返回一個清理函式
-
使用 setTimeout 代替 setInterval(還是要使用清理函式)
-
使用 setCount 的函式形式來避免直接引用當前值
事實上哪種辦法都行得通。我們在這裡實現最後一個選項:
<code data-type="codeline">function Counter() {</code><code data-type="codeline"> const [count, setCount] = useState(0);</code><code data-type="codeline"> useEffect(() => {</code><code data-type="codeline"> setInterval(() => {</code><code data-type="codeline"> setCount((count) => count + 1);</code><code data-type="codeline"> }, 1000);</code><code data-type="codeline"> }, []);</code><code data-type="codeline"> return <div>The count is: {count}</div>;</code><code data-type="codeline">}</code>
複製程式碼
我們的計數器修好了!由於依賴陣列中沒有任何內容,因此我們只建立了一個間隔。由於我們為計數設定器使用了回撥函式,因此永遠不會在 count 變數上有陳舊閉包。
這是一個人為做出來的例子,但除非你已經使用 React 一段時間,否則它仍然很令人困惑。我們中有許多人每天都會遇到更復雜的情況,即使是最有經驗的 React 開發人員也會為之頭痛不已。
假的響應性
我思考了很多關於 hooks 的事情,想知道為什麼它們感覺不太對勁。結果我通過探索 Solid.js 找到了答案。
React hooks 的問題在於 React 並不是真正的響應式設計。如果 linter 知道一個效果(或回撥或 memo)hook 何時缺少依賴項,那麼為什麼框架不能自動檢測依賴項並對這些更改做出響應呢?
深入研究 Solid.js
關於 Solid,首先要注意的是它沒有嘗試重新發明輪子:它看起來很像 React,因為 React 有一些顯眼的模式:單向、自上而下的狀態;JSX;元件驅動的架構。
如果我們用 Solid 重寫 Counter 元件,會這樣開始:
<code data-type="codeline">function Counter() {</code><code data-type="codeline"> const [count, setCount] = createSignal(0);</code><code data-type="codeline"> return <div>The count is: {count()}</div>;</code><code data-type="codeline">}</code>
複製程式碼
到目前為止我們看到了一個很大的不同點:count 是一個函式。這稱為訪問器(accessor),它是 Solid 工作機制的重要組成部分。當然,我們這裡沒有關於按間隔遞增 count 的內容,所以下面把它新增進去:
<code data-type="codeline">function Counter() {</code><code data-type="codeline"> const [count, setCount] = createSignal(0);</code><code data-type="codeline"> setInterval(() => {</code><code data-type="codeline"> setCount(count() + 1);</code><code data-type="codeline"> }, 1000);</code><code data-type="codeline"> return <div>The count is: {count()}</div>;</code><code data-type="codeline">}</code>
複製程式碼
這肯定行不通,對吧?每次元件渲染時不會設定新的間隔嗎?
沒有。它就這麼正常運行了。
但為什麼會這樣?好吧,事實證明 Solid 不需要重新執行 Counter 函式來重渲染新的計數。事實上,它根本不需要重新執行 Counter 函式。如果我們在 Counter 函式中新增一個 console.log 語句,就會看到它只執行一次。
<code data-type="codeline">function Counter() {</code><code data-type="codeline"> const [count, setCount] = createSignal(0);</code><code data-type="codeline"> setInterval(() => {</code><code data-type="codeline"> setCount(count() + 1);</code><code data-type="codeline"> }, 1000);</code><code data-type="codeline"> console.log('The Counter function was called!');</code><code data-type="codeline"> return <div>The count is: {count()}</div>;</code><code data-type="codeline">}</code>
複製程式碼
在我們的控制檯中,只有一個孤獨的日誌語句:
"The Counter function was called!"
複製程式碼
在 Solid 中,除非我們明確要求,否則程式碼不會多次執行。
但是 hooks 呢?
於是我在 Solid 中解決了 React useEffect hook 的問題,而無需編寫看起來像 hooks 的東西。我們可以擴充套件我們的計數器例子來探索 Solid 效果。
如果我們想在每次計數增加時 console.log count 怎麼辦?你的第一反應可能是在我們的函式中使用 console.log:
<code data-type="codeline">function Counter() {</code><code data-type="codeline"> const [count, setCount] = createSignal(0);</code><code data-type="codeline"> setInterval(() => {</code><code data-type="codeline"> setCount(count() + 1);</code><code data-type="codeline"> }, 1000);</code><code data-type="codeline"> console.log(`The count is ${count()}`);</code><code data-type="codeline"> return <div>The count is: {count()}</div>;</code><code data-type="codeline">}</code>
複製程式碼
但這不起作用。請記住,Counter 函式只執行一次!但我們可以使用 Solid 的 createEffect 函式來獲得想要的效果:
<code data-type="codeline">function Counter() {</code><code data-type="codeline"> const [count, setCount] = createSignal(0);</code><code data-type="codeline"> setInterval(() => {</code><code data-type="codeline"> setCount(count() + 1);</code><code data-type="codeline"> }, 1000);</code><code data-type="codeline"> createEffect(() => {</code><code data-type="codeline"> console.log(`The count is ${count()}`);</code><code data-type="codeline"> });</code><code data-type="codeline"> return <div>The count is: {count()}</div>;</code><code data-type="codeline">}</code>
複製程式碼
這行得通!而且我們甚至不必告訴 Solid,說這個效果取決於 count 變數。這才是真正的響應式設計。如果在 createEffect 函式內部呼叫了第二個訪問器,它也會讓效果執行起來。
一些更有趣的 Solid 概念
響應性,而不是生命週期 hooks
如果你已經在 React 領域有一段時間的經驗了,那麼下面的程式碼更改可能真的會讓你大跌眼鏡:
<code data-type="codeline">const [count, setCount] = createSignal(0);</code><code data-type="codeline">setInterval(() => {</code><code data-type="codeline"> setCount(count() + 1);</code><code data-type="codeline">}, 1000);</code><code data-type="codeline">createEffect(() => {</code><code data-type="codeline"> console.log(`The count is ${count()}`);</code><code data-type="codeline">});</code><code data-type="codeline">function Counter() {</code><code data-type="codeline"> return <div>The count is: {count()}</div>;</code><code data-type="codeline">}</code>
複製程式碼
並且程式碼仍然是有效的。我們的 count 訊號不需要存在於一個元件函式中,依賴它的效果也不需要。一切都只是響應式系統的一部分,“生命週期 hooks”實際上並沒有起到太大的作用。
細粒度的 DOM 更新
前面我主要關注的是 Solid 的開發體驗(例如更容易編寫沒有錯誤的程式碼),但 Solid 的效能表現也得到了很多讚譽。其強大效能的一個關鍵來源是它直接與 DOM 互動(無虛擬 DOM)並執行“細粒度”的 DOM 更新。
考慮對我們的計數器進行以下調整:
<code data-type="codeline">function Counter() {</code><code data-type="codeline"> const [count, setCount] = createSignal(0);</code><code data-type="codeline"> setInterval(() => {</code><code data-type="codeline"> setCount(count() + 1);</code><code data-type="codeline"> }, 1000);</code><code data-type="codeline"> return (</code><code data-type="codeline"> <div></code><code data-type="codeline"> The {(console.log('DOM update A'), false)} count is:{' '}</code><code data-type="codeline"> {(console.log('DOM update B'), count())}</code><code data-type="codeline"> </div></code><code data-type="codeline"> );</code><code data-type="codeline">}</code>
複製程式碼
執行它會在控制檯中獲得以下日誌:
<code data-type="codeline">DOM update A</code><code data-type="codeline">DOM update B</code><code data-type="codeline">DOM update B</code><code data-type="codeline">DOM update B</code><code data-type="codeline">DOM update B</code><code data-type="codeline">DOM update B</code><code data-type="codeline">DOM update B</code>
複製程式碼
換句話說,每秒更新的唯一內容是包含 count 的一小部分 DOM。Solid 甚至沒有重新運行同一 div 中較早的 console.log。
小結
在過去的幾年裡我很喜歡使用 React;在處理實際的 DOM 時,我總感覺它有著正確的抽象級別。話雖如此,我也開始注意到 React hooks 程式碼經常變得容易出錯。我感覺 Solid.js 使用了 React 的許多符合人體工程學的部分,同時最大程度減少了混亂和錯誤。本文向你展示的是 Solid 的一些讓我驚歎的部分,感興趣的話我建議你檢視 https://www.solidjs.com 並自己探索這個框架。
- flutter 系列之:flutter 中的 flow
- Eureka 註冊資訊配置備忘
- 巧用 redis 實現點贊功能,它不比 mysql 香嗎?
- Linux 開發 _ 攝像頭程式設計 (實現拍照、網頁監控功能)
- Electron 在作業幫直播課 PC 學生端的實踐
- 物聯網協議的王者:MQTT
- 解密安卓微信聊天資訊儲存
- 首都線上龔巨集績:用生態賦能企業出海 實現多方共贏 | TGO 專訪
- GPT-4 都快出來了, GPT-3 的一些缺陷仍然被詬病
- 實戰監聽 Eureka client 的快取更新
- 初識 ElastricSearch
- InfoQ 2022 年趨勢報告:DevOps 與雲端計算篇
- 拒絕八股文!這篇圖解動態路由分分鐘愛了
- 從使用者走向引領者,如何加速國產開源生態建設?
- Docker 實踐經驗(二)映象的構建、映象倉庫、壓縮、匯入
- 雲原生之 Ansible 篇(一)
- ElastricSearch 第二彈之分片原理
- 超影片時代音影片架構建設與演進
- 騰訊內容結算下一代系統探索實踐
- 10 分鐘,帶你瞭解 3 篇 SIGMOD、WWW 等資料庫頂會論文的研究成果