許多初學(xué)者經(jīng)常會(huì)問(wèn) “我需要學(xué)習(xí)哪個(gè)框架 株婴?” 以及 “學(xué)習(xí)框架前需要掌握多少 JS 或者 TS ?” 無(wú)數(shù)帶有主觀色彩的文章都在宣傳作者首選框架或庫(kù)的優(yōu)勢(shì),而不是向讀者展示其背后的概念以做出更明智的決定。所以讓我們先解決第二個(gè)問(wèn)題
學(xué)習(xí)框架前需要掌握多少 JS 或者 TS
盡可能多地去學(xué)以讓更好的你理解它們所基于的概念旺坠。你將需要了解基本數(shù)據(jù)類(lèi)型、函數(shù)扮超、基本運(yùn)算符和文檔對(duì)象模型 ( DOM )取刃,這是 HTML 和 CSS 在 JS 中的表示。除此之外的一切也都 OK出刷,但并不嚴(yán)格要求某個(gè)精通框架或庫(kù)璧疗。
如果你是一個(gè)完完全全的新手,JS for cats?應(yīng)該是一個(gè)不錯(cuò)的入門(mén)資料馁龟。持續(xù)學(xué)習(xí)崩侠,直到你感到自信為止,然后繼續(xù)前進(jìn)坷檩,直到你再次感到自信為止却音。當(dāng)掌握了足夠的 JS / TS 知識(shí)后改抡,你就可以開(kāi)始學(xué)習(xí)框架。其他的知識(shí)你可以并行學(xué)習(xí)僧家。
哪些重要概念
State (狀態(tài))
Effects (副作用)
Memoization (記憶化)
Templating and rendering (模板與渲染)
所有現(xiàn)代框架都從這些概念中派生出它們的功能
state
State 只是為你的應(yīng)用程序提供動(dòng)力的數(shù)據(jù)雀摘。它可能在全局級(jí)別,適用于應(yīng)用程序的大部分組件八拱,或適用于單個(gè)組件。讓我們寫(xiě)一個(gè)計(jì)數(shù)器的簡(jiǎn)單例子來(lái)說(shuō)明一下涯塔。它保留的計(jì)數(shù)是 state 肌稻。我們可以讀取 state 或者寫(xiě)入 state 以增加計(jì)數(shù)
最簡(jiǎn)單的表示通常是一個(gè)變量,其中包含我們的狀態(tài)所包含的數(shù)據(jù):
letcount =0;constincrement= () => { count++; };constbutton =document.createElement('button'); button.textContent= count; button.addEventListener('click', increment);document.body.appendChild(button);
但這個(gè)代碼有個(gè)問(wèn)題:類(lèi)似調(diào)用?increment?方法一樣去修改?count?的值 爹耗,并不會(huì)自動(dòng)修改 button 的文案苔悦。我們需要手動(dòng)去更新所有的內(nèi)容奏寨,但這樣的做法在復(fù)雜場(chǎng)景下代碼的可維護(hù)性 & 擴(kuò)展性都不是很好。
讓?count?自動(dòng)更新依賴(lài)它的使用方的能力稱(chēng)之為?reactivity(響應(yīng)式)?诺凡。這是通過(guò)訂閱并重新運(yùn)行應(yīng)用程序的訂閱部分來(lái)更新的。
幾乎所有的現(xiàn)代前端框架和庫(kù)都擁有讓 state 變成 reactivity 的能力践惑「姑冢基本上可以分為 3 種解決方案,采用其中至少一種或者多種混用來(lái)實(shí)現(xiàn)這個(gè)能力:
Observables / Signals (可觀察的 / 信號(hào))
Reconciliation of immutable updates (協(xié)調(diào)不可變的更新)
Transpilation (轉(zhuǎn)譯)
這些概念還是直接用英文表達(dá)比較貼切 ??
Observables / Signals (可觀察的 / 信號(hào))
Observables 基本上是在讀取 state 的時(shí)候通過(guò)一個(gè)訂閱方法來(lái)收集依賴(lài)尔觉,然后在更新的時(shí)候觸發(fā)依賴(lài)的更新
conststate= (initialValue) => ({_value: initialValue,get:function() {/* 訂閱 */;returnthis._value;? },set:function(value) {this._value= value;/* 觸發(fā)更新 */;? }});
knockout?是最早使用這個(gè)概念的框架之一凉袱,它使用帶有 / 不帶參數(shù)的相同函數(shù)進(jìn)行寫(xiě)/讀訪問(wèn)
這種模式最近有開(kāi)始有框架通過(guò) signals 來(lái)實(shí)現(xiàn),比如?Solid.js?和preact signals?侦铜;相同的模式也在?Vue?和Svelte?中使用到专甩。RxJS 為 Angular 的 reactive 層提供底層能力,是這一模式的延伸钉稍,超越了簡(jiǎn)單狀態(tài)涤躲。Solid.js?用 Stores(一些通過(guò) setter 方法來(lái)操作的對(duì)象)的方式進(jìn)一步抽象了 signals
Reconciliation of immutable states(協(xié)調(diào)不可變的更新)
不可變意味著如果對(duì)象的某個(gè)屬性發(fā)生改變,那么整個(gè)對(duì)象的引用就會(huì)發(fā)生改變贡未。所以協(xié)調(diào)器做的事情就包括通過(guò)簡(jiǎn)單的引用對(duì)比就判斷出對(duì)象是否發(fā)生了改變
conststate1 = {todos: [{text:'understand immutability',complete:false}],currentText:''};// 更新 currentText 屬性conststate2 = {todos: state1.todos,currentText:'understand reconciliation'};// 添加一個(gè) todoconststate3 = {todos: [? ? state1.todos[0],? ? {text:'understand reconciliation',complete:true}? ],currentText:''};// 由于不可變性种樱,這里將會(huì)報(bào)錯(cuò)state3.currentText='I am not immutable!';
如你所見(jiàn),未變更項(xiàng)目的引用被重新使用羞秤。如果協(xié)調(diào)器檢測(cè)到不同的對(duì)象引用缸托,那么它將重新運(yùn)行所有的組件,讓所有的組件的 state (props, memos, effects, context) 都使用最新的這個(gè)對(duì)象瘾蛋。由于讀取訪問(wèn)是被動(dòng)的俐镐,所以需要手動(dòng)指定對(duì)響應(yīng)值的依賴(lài)。
很顯然哺哼,你不會(huì)用上面這種方式定義 state 佩抹。要么你是從一個(gè)已經(jīng)存在的屬性構(gòu)造 state 叼风,要么你會(huì)使用?reducer?來(lái)構(gòu)造 state。一個(gè) reducer 函數(shù)就是接收一個(gè) state 對(duì)象然后返回一個(gè)新的 state 對(duì)象棍苹。
react和preact?就使用這種模式无宿。它適合與 vDOM 一起使用,我們將在稍后描述模板時(shí)探討它枢里。
并不是所有的框架都借助 vDOM 將 state 變成完成響應(yīng)式孽鸡。例如?Mithril.JS?要不是在 state 修改后觸發(fā)對(duì)應(yīng)的生命周期事件,要不是手動(dòng)調(diào)用?m.redraw()?方法栏豺,才能夠觸發(fā)更新
Transpilation(轉(zhuǎn)譯)
Transpilation 是在構(gòu)建階段彬碱,重寫(xiě)我們的代碼讓代碼可以在舊的瀏覽器運(yùn)行或者賦予代碼其他的能力;在這種情況下奥洼,轉(zhuǎn)譯則是被用于把一個(gè)簡(jiǎn)單的變量修改成響應(yīng)式系統(tǒng)的一部分巷疼。
Svelte?就是基于轉(zhuǎn)譯器,該轉(zhuǎn)譯器還通過(guò)看似簡(jiǎn)單的變量聲明和訪問(wèn)為他們的響應(yīng)式系統(tǒng)提供能力
另外灵奖,Solid.js?也是使用 Transpilation 嚼沿,但 Transpilation 只使用到模版上,沒(méi)有使用到 state 上
Effects
大部分情況下瓷患,我們需要做的更多是操作響應(yīng)式的 state骡尽,而很少需要操作基于 state 的 DOM 渲染。我們需要管理好副作用尉尾,這些副作用是由于視圖更新之外的狀態(tài)變化而發(fā)生的所有事情(雖然有些框架把視圖更新也當(dāng)作是副作用爆阶,例如
?https://b23.tv/5CVC7NX
https://b23.tv/BY7NBFf
https://b23.tv/0eHr98g
https://b23.tv/qVGZIUB
記得之前 state 的例子中,我們故意把訂閱操作的代碼留空∩秤剑現(xiàn)在讓我們把這些留空補(bǔ)齊來(lái)處理副作用辨图,讓程序能夠響應(yīng)更新
constcontext = [];conststate= (initialValue) => ({_subscribers:newSet(),_value: initialValue,get:function() {constcurrent = context.at(-1);if(current) {this._subscribers.add(current); }returnthis._value;? },set:function(value) {if(this._value=== value) {return; }this._value= value;this._subscribers.forEach(sub=>sub());? }});consteffect= (fn) => {constexecute= () => {? ? context.push(execute);try{fn(); }finally{ context.pop(); }? };execute();};
上面代碼基本上是對(duì)?preact signals或者Solid.js?響應(yīng)式 state 的簡(jiǎn)化版本,它不包含錯(cuò)誤處理和復(fù)雜狀態(tài)處理(使用一個(gè)函數(shù)接收之前的狀態(tài)值肢藐,返回下一個(gè)狀態(tài)值)故河,但這些都是很容易就可以加上的