Solid 作者從 React 中學(xué)到最重要的是什么?

大家好,我卡頌。
前端界有句玩笑話 —— 「React 一點(diǎn)都不 react,Solid 才應(yīng)該叫 React」。
作為一款「借鑒了很多 React 特性」的前端框架,截止目前,Solid已經(jīng)有 29.6kstar。顯然,他已經(jīng)得到了社區(qū)的認(rèn)可。

前段時(shí)間,Solid的作者「Ryan Carniato」在博文Thinking Locally with Signals[1]中提到 —— Solid從React中學(xué)到的最重要的東西,不是JSX、虛擬DOM,而是一個(gè)被稱為「局部思考」(Locality of Thinking)的概念。
本文就來(lái)聊聊這個(gè)對(duì)前端開(kāi)發(fā)影響深遠(yuǎn)的理念。
局部思考是什么?
當(dāng)我們新入職一家公司,在熟悉項(xiàng)目代碼階段,領(lǐng)導(dǎo)通常會(huì)分配給我們一些小需求,幫助我們快速熟悉項(xiàng)目代碼。
這個(gè)過(guò)程是如此自然,以至于我們都忽視了一個(gè)重要問(wèn)題 —— 為什么在一個(gè)龐大的項(xiàng)目代碼庫(kù)中,即使不熟悉代碼,也能輕松修改一些小功能?
答案是 —— 「局部思考」理念在發(fā)揮作用。
「局部思考」是指你可以不看其他代碼,只通過(guò)一個(gè)組件的代碼就能理解它的行為。這種思考方式對(duì)代碼的可維護(hù)性和可讀性有著重大影響。
首先,在大型項(xiàng)目中,代碼的「可維護(hù)性」至關(guān)重要。如果每次修改都需要理解整個(gè)代碼庫(kù),那么這個(gè)項(xiàng)目可能會(huì)很快變得難以維護(hù)。
其次,從「可讀性」的角度來(lái)看,如果代碼的可讀性好,那么新的開(kāi)發(fā)人員可以更快地理解和開(kāi)始他們的工作。
通過(guò)「局部思考」,可以使代碼更易讀、可維護(hù)性更高。試想,這不正是「使用框架開(kāi)發(fā)」相比于「使用 jQuery 開(kāi)發(fā)」的優(yōu)勢(shì)么?至于框架的其他特性(比如虛擬DOM、細(xì)粒度更新、Hooks...)都是在「局部思考」的基礎(chǔ)上發(fā)展出來(lái)的。
可以說(shuō),「局部思考」是「框架開(kāi)發(fā)」這種工作模式的基石。
如何實(shí)現(xiàn)局部思考
假設(shè)項(xiàng)目中有如下代碼,你能保證結(jié)果是true么:
const obj = {}
someFunction(obj)
// 結(jié)果是 true 么?
console.log(obj.value === undefined)要想知道結(jié)果,必須看someFunction函數(shù)的內(nèi)部實(shí)現(xiàn)。如果項(xiàng)目中大量充斥了上面這樣的代碼,對(duì)可讀性、可維護(hù)性簡(jiǎn)直是災(zāi)難。
「局部思考」理念的提出就是為了解決上述問(wèn)題。要實(shí)現(xiàn)「局部思考」,有四個(gè)重要因素:
- 單向數(shù)據(jù)流
- 讀寫(xiě)分離
- 顯式突變
- 組件隔離
單向數(shù)據(jù)流
數(shù)據(jù)應(yīng)該只在一個(gè)方向上流動(dòng)。這樣可以保證數(shù)據(jù)的來(lái)源和使用是一致的,使得代碼行為更可預(yù)測(cè),減小了出現(xiàn)bug的可能性。
考慮如下Solid代碼,數(shù)據(jù)只從父組件流向子組件。子組件只讀取數(shù)據(jù),而不能改變它:
// 父組件內(nèi)
const [count, setCount] = createSignal(0);
<ChildComponent count={count()} />
// 子組件內(nèi)
const ChildComponent = ({ count }) => {
// count是只讀的
return <div>{count}</div>
}讀寫(xiě)分離
讀取數(shù)據(jù)和修改數(shù)據(jù)應(yīng)該是兩個(gè)獨(dú)立的操作。這樣可以降低代碼的復(fù)雜度,使得閱讀和理解代碼更簡(jiǎn)單。
考慮如下Solid代碼,SomeComponent通過(guò)title()讀取值,通過(guò)setTitle修改值。這種分離使得我們可以更好地理解狀態(tài)何時(shí)變化。
// [讀, 寫(xiě)]
const [title, setTitle] = createSignal("title");
// `SomeComponent`不能改變`title`
<SomeComponent title={title()} />
// 現(xiàn)在`SomeComponent`可以更新title
<SomeComponent title={title()} updateTitle={setTitle} />在Svelte中,狀態(tài)(或者叫signal)只能「按值傳遞」,所以下面SomeComponent即使接收title作為props,也無(wú)法直接修改他。
要修改他,需要執(zhí)行updateTitle方法(方法內(nèi)部閉包中的title是signal,可以響應(yīng)更新)。這也是一種「讀寫(xiě)分離」的實(shí)現(xiàn)。
let title = $state("title")
// `SomeComponent`不能改變`title`
<SomeComponent title={title} />
// 現(xiàn)在`SomeComponent`可以更新title
<SomeComponent title={title} updateTitle={(v) => title = v} />顯式突變
所有的數(shù)據(jù)變化應(yīng)該是顯式的,而不是在背后默默發(fā)生。這樣更容易跟蹤數(shù)據(jù)的變化??紤]如下Solid代碼:
// 定義狀態(tài)
const [count, setCount] = createSignal(0);
// 顯式改變狀態(tài)
setCount(count() + 1);是不是一下就想到了React中的useState呢?沒(méi)錯(cuò),其實(shí)不止是useState,在ClassComponent的this.setState也是遵循同樣的原則。
組件隔離
每個(gè)組件應(yīng)該只關(guān)心自己的狀態(tài)和邏輯,而不是其他組件的。這樣可以保證組件之間的獨(dú)立性,降低耦合度,使得代碼更易于維護(hù)。
總結(jié)
如果你覺(jué)得以上的介紹一點(diǎn)技術(shù)含量都沒(méi)有,那是再自然不過(guò)的事。因?yàn)檫@些原則都是React最基本的使用規(guī)范。
本文存在的意義,是闡述一個(gè)觀點(diǎn) —— 這些規(guī)范之所以存在,是為了共同實(shí)現(xiàn)「局部思考」的理念。而這一理念,才是前端框架可維護(hù)性、可讀性的來(lái)源。
按照這個(gè)思路去思考,就能明白很多React特性的用意,比如:
- 為什么函數(shù)組件替代了類組件。
- 為什么會(huì)出現(xiàn)useEffect這個(gè)Hook。
- 為什么ref不能跨函數(shù)組件傳遞。
這些特性的背后,都體現(xiàn)了「局部思考」的理念。
參考資料
[1]Thinking Locally with Signals:https://dev.to/this-is-learning/thinking-locally-with-signals-3b7h。



























