你們好,我是卡·小學生·頌。前端
很開心還有不到10天小學就放暑假了,到時候打農藥被人噴了就能說:編程
「我媽只准我放假玩,手有點兒生」數組
說回前端。前端框架
其實前端框架是個很簡單的東西,大部分框架的工做原理能夠用一個小學知識解釋清楚。markdown
本文會從這個知識入手,逐步談到前端框架更新粒度背後的權衡。框架
看完本文,不但能收穫一個審視不一樣框架的視角,也能瞭解React Hooks
產生的緣由。函數
可不要再瞧不起咱們小學生哦!學習
這個小學知識就是:自變量
與因變量
。ui
對於以下等式:this
2x + 1 = y
複製代碼
x
是自變量
,y
的值受x
影響,是因變量
。
在不少框架與狀態管理庫中,一樣存在自變量與因變量。
Vue3
中的自變量:
const x = value(1);
// 取值
console.log(x.value);
// 賦值
x.value = 2;
複製代碼
MobX
的自變量:
const x = observable({data: 1});
// 取值
console.log(x.data);
// 賦值
x.data = 2;
複製代碼
React
的自變量:
const [x, setX] = useState(1);
// 取值
console.log(x);
// 賦值
setX(2);
複製代碼
這些框架(或庫)的自變量由getter
(取值)與setter
(賦值)兩部分構成。
有了自變量
,固然也有因變量
。咱們能夠按有無反作用區分。
Vue
的因變量:
// 無反作用
// 賦值
const y = computed(() => x.value * 2 + 1);
// 取值
console.log(y.value);
// 有反作用
watch(() => document.title = x.value);
複製代碼
MobX
的因變量:
// 無反作用
const y = computed(() => x.data * 2 + 1);
console.log(y.get());
// 有反作用
autorun(() => document.title = x.data);
複製代碼
React
的因變量:
// 無反作用
const y = useMemo(() => x * 2 + 1, [x]);
console.log(y);
// 有反作用
useEffect(() => document.title = x, [x]);
複製代碼
有了自變量
與因變量
,再配合描述視圖的方式
,就能描述組件UI
。
好比在React
中,經過JSX
描述視圖:
const [x, setX] = useState(21);
const y = useMemo(() => x * 2 + 1, [x]);
return <p>個人戰績是 0/{x}/{y}</p>;
複製代碼
再加上各類使用戶能夠操縱自變量
的事件,如給p
增長onClick
:
<p onClick={() => setX(x + 1)}>個人戰績是 0/{x}/{y}</p>;
複製代碼
最後再加上少許輔助的鉤子函數,如:組件發生錯誤時的鉤子函數。
就組成一個功能完備的組件。
這就是全部細粒度更新框架的底層共通之處:
經過事件驅動自變量改變,進而最終驅動視圖(或反作用)變化
在咱們初學編程時,都學過一個概念 —— 面向對象(下文簡稱OO
),也很容易接受一個設定 —— OO
能夠提升可讀性且易維護。
緣由是:OO
是對現實世界的模擬。好比:
人能夠繼承哺乳動物的屬性,這就是個
OO
模型
然而實際操做起來卻事與願違。
回想你學習React
的Class
組件時,在OO
簡單的表象背後,是複雜的生命週期概念,隨便問你幾個問題:
shouldComponentUpdate
的原理是?
componentWillReceiveProps
何時觸發?
getDerivedStateFromProps
中derivedState
是什麼意思?
好在React
團隊也意識到這個問題,並着手作出改變。
改變的結果,就是Hooks
。
使用Hooks
的函數組件與Class
組件最大的區別是:
從擁有生命週期的實例向自變量、因變量與視圖的映射關係轉變
若是接受了這個設定,想一想如今主流的學習Hooks
的方式(甚至React
官網也是如此),竟然是:
用生命週期函數來類比
Hooks
執行時機
是否是還蠻搞笑的。
理想是美好的,但是React
底層並非一個細粒度框架。
這就形成在實現自變量
、因變量
時會有諸多限制,好比:
Hooks
調用順序不能變(不能寫在條件語句中)
再好比,不知道你發現一個細節沒:
React
實現因變量
時須要第二個參數顯式指出本身的自變量
是誰。好比:
const y = useMemo(() => x * 2 + 1, [x]);
useEffect(() => document.title = x, [x]);
複製代碼
反觀其餘框架(或庫)就不須要。好比Vue
:
const y = computed(() => x.value * 2 + 1);
watch(() => document.title = x.value);
複製代碼
爲何會有這些限制呢?我用兩個比喻來解釋。
剛纔聊到,在細粒度
框架中,交互流程能夠歸納爲:
用戶觸發事件 -> 自變量改變 -> 因變量改變 -> 映射到視圖變化
複製代碼
就像一個畫家在畫畫,畫的每一筆對應一個自變量
變化,再最終對應畫面變化。
而React
的更新機制大致歸納爲:
用戶觸發事件 -> 觸發更新 -> 虛擬DOM全量對比 -> 將對比結果映射爲視圖操做
複製代碼
就像一我的拿相機拍一張照片,再拿這張照片和上次拍的照片找不一樣,最後把不一樣的地方更新了。
當調用this.setState
(或useState
的setter
),並非畫下一筆,而是按下快門。
怎麼能從一張新照片中發現自變量
呢?因此React
只能拿新老照片對比。
社區早有人意識到這個問題,因而Mobx
誕生了。他帶來純粹的細粒度更新能力。
然而,這個能力是創建在React
更新機制之上,就像:
一個畫家,拿畫筆在畫板上一戳,戳到什麼呢?戳到相機快門了。
咔嚓拍了張照片,畫家再拿照片與老照片對比,將對比結果再畫到畫布上。
因此有人吐槽:用React
+Mobx
爲啥不直接用Vue
?
然而,Vue
自己也依賴虛擬DOM
,粒度並非最細的。
更準確的說法應該是:用React
+Mobx
爲啥不直接用SolidJS
?
吶,過幾天咱們來聊聊純粹的細粒度更新框架(SolidJS
)的實現原理吧。