大家好,我卡頌。
有3個容易混淆的前端框架概念:
- 響應式更新
- 單向數據流
- 雙向數據綁定
在繼續閱讀本文前,讀者可以思考下是否明確知道三者的含義。
這三者之所以容易混淆,是因爲他們雖然同屬前端框架範疇內的概念,但又不是同一抽象層級的概念,不好直接比較。
本文會從3個抽象層級入手講解這三者的區別。
歡迎加入人類高質量前端交流羣,帶飛
響應式更新
響應式更新也叫細粒度更新。同時,最近前端圈比較火的Signal
這一概念描述的也是響應式更新。
籠統的講,響應式更新描述的是狀態與UI之間的關係,即狀態變化如何映射到UI變化。
考慮如下例子(例子來自what are signals一文):
function TodoApp() {
const [todos, setTodos] = useState(
[{ text: 'sleep', completed: false }]
)
const [showCompleted, setShowCompleted] = useState(false)
const filteredTodos = useMemo(() => {
return todos.filter((todo) => !todo.completed || showCompleted)
}, [todos, showCompleted])
return (
<TodoList todos={filteredTodos} />
)
}
在TodoApp
組件中,定義了兩個狀態:
- 待辦事項
todos
- 是否展示完成的事項
showCompleted
以及根據上述狀態派生出的狀態filteredTodos
。最終,返回<TodoList/>
組件。
如果todos
狀態變化,UI
該如何變化?即我們該如何知道狀態變化的影響範圍?這時,有兩個思路:
- 推(
push
) - 拉(
pull
)
推的原理
我們可以從變化的狀態(例子中爲todos
)出發,根據狀態的派生關係,一路推下去。
在例子中:
-
todos
變化 -
filteredTodos
由todos
派生而來,變化傳導到他這裏 -
<TodoList/>
組件依賴了filteredTodos
,變化傳導到他這裏 - 確定了
todos
變化的最終影響範圍後,更新對應UI
這就建立了狀態與UI之間的關係。
除了推之外,還有一種被稱爲拉的方式。
拉的原理
同樣的例子,我們也能建立狀態與可能的UI變化的關係,再反過來推導UI
變化的範圍。
在例子中:
-
todos
變化 - 可能有
UI
變化(因爲建立了狀態與可能的UI變化的關係) -
UI
與<TodoList/>
組件相關,判斷他是否變化 -
<TodoList/>
組件依賴filteredTodos
,filteredTodos
由todos
派生而來,所以filteredTodos
是變化的 - 既然
filteredTodos
變化了,那麼<TodoList/>
組件可能變化 - 計算變化的影響範圍,更新
UI
在主流框架中,React
的更新以推爲主,Vue
、Preact
、Solid.js
等更多框架使用拉的方式。
本文聊的響應式更新就是拉這種方式的一種實現。
單向數據流
我們可以發現,不管是推還是拉,他們都需要計算變化的影響範圍,即一個狀態變化後,究竟有多少組件會受影響。
那麼,從框架作者的角度出發,是希望增加一些約束,來減少計算影響範圍這一過程的複雜度。
同樣,從框架使用者的角度出發,也希望增加一些約束,當計算影響範圍出bug
後,更容易排查問題。
這就有了單向數據流。
單向數據流是一條約定,他規定了當狀態變化後,變化產生的影響只會從上往下傳遞。
考慮如下例子:
function Parent() {
const [num] = useState(0);
return <Child data={num}/>;
}
function Child({data}) {
const isEven = data % 2 === 0;
return <GrandChild data={isEven}/>;
}
function GrandChild({data}) {
return <p>{data}</p>;
}
<Parent/>
組件的狀態num
作爲props
傳給<Child/>
組件,再作爲props
傳給<GrandChild/>
組件,整個過程只能自上而下。
單向數據流並不是實現前端框架必須遵循的原則,他的存在主要是爲了減少開發者的心智負擔,讓狀態變化後,計算影響範圍這一過程更可控。
雙向數據綁定
當本文開篇聊響應式更新時,討論的是狀態與UI的關係,這是將框架作爲一個整體來討論,抽象層級比較高。
當我們繼續聊到單向數據流時,討論的是狀態變化的影響範圍在組件間單向擴散,這是組件與組件之間的關係,抽象層級下降了一級。
接下來我們要討論的雙向數據綁定,討論的是單個組件內發生的事。
雙向數據綁定是狀態+改變狀態後觸發的回調相結合的語法糖。
這裏不討論框架語境下「語法糖」一詞是否完全準確
比較知名的雙向數據綁定實現,比如Vue
中的v-model
語法:
<input v-model=‘data’/>
相當於如下狀態+事件回調的組合:
<input @input='onInput' :value=‘data’ />
實際上早期
React
中也有類似實現,名叫LinkedStateMixin,只是早已被廢棄
總結
我們可以用一張圖概括本文介紹的3個概念之間的關係:
概括起來主要是兩點:
- 他們都是前端框架範疇內的概念
- 他們屬於不同抽象層級的概念
其中:
- 雙向數據綁定描述的是組件內邏輯與視圖的關係
- 單向數據流描述的是組件之間的關係
- 響應式更新描述的是狀態與UI之間的關係