設計思想
想表達什麼?React 怎樣理解 Application?
應用是個狀態機,狀態驅動視圖
v = f(d)
v是视图
f是组件
d是数据/状态
與 FP 有什麼關係?
把函式式思想引入前端,透過 PureComponent 組合來實作 UI
最大好處是讓 UI 可預測,對同樣的 f 輸入同樣的 d 一定能得到同樣的 v
可以把各個 f 單獨拎出來測試,組合起來肯定沒有問題,從理論上確定了元件品質是可靠的,組合出來的整個應用的 UI 也是可靠的
目標
想解決什麼問題?定位?
A JAVASCRIPT LIBRARY FOR BUILDING USER INTERFACES
針對構建 UI 提供一種元件化的方案
能解決什麼問題?
-
元件化
-
UI 可靠性
-
資料驅動視圖
效能目標
For many applications, using React will lead to a fast user interface without doing much work to specifically optimize for performance.
尋找成本與收益的平衡點,不刻意去做效能優化,還能寫出*效能不錯(非最優)*的應用
實際上,React 所作的效能優化主要體現在:
-
事件代理,全域一個事件監聽
-
自己有完整的捕獲冒泡,是為了抹平 IE8 的 bug
-
物件池複用 event 物件,減少 GC
-
-
DOM 操作整合,減少次數
但無論怎樣,效能肯定不及年邁的(經驗豐富的)FEer 手寫的原生 DOM 操作版
虛擬 DOM
透過什麼方式解決問題?
在 DOM 樹之上加一層額外的抽象
元件化方式:提供元件 class 範本、生命週期 hook、資料流轉方式、局部狀態代管
執行時:用虛擬 DOM 樹管理元件,建立並維護到真實 DOM 樹的映射關係
虛擬 DOM 有什麼作用?
-
批次處理提升效能
-
降低 diff 開銷
-
實作「資料繫結」
具體實作
JSX -> React Element -> 虚拟DOM节点 ..> 真实DOM节点
描述对象
-
編譯時,翻譯 JSX 得到
createElement -
執行
createElement得到 React Element 描述物件 -
根據描述物件建立虛擬 DOM 節點
-
整合虛擬 DOM 節點上的狀態,建立真實 DOM 節點
虛擬 DOM 樹的節點集合是真實 DOM 樹節點集合的超集,多出來的部分是自定義元件(Wrapper)
結構上,內部樹佈局是森林,維護在 instancesByReactRootID:
-
現有 app 引入 React 時,會有多個 root DOM node
-
純 React 的應用,森林裡一般只有 1 棵樹
單向資料流
瀑布模型
由 props 和 state 把元件組織起來,元件間資料流向類似於瀑布
資料流向總是從祖先到子孫(從根到葉子),不會逆流
-
props:管道 -
state:水源
單向資料流是由狀態丟棄機制決定的,具體表現為:
-
狀態變化引發的資料及 UI 變化都只會影響下方的元件
-
渲染視圖時向下流,表單互動能回來,引發另一次向下渲染
單向資料流是對渲染視圖過程而言的,子孫的 state 如何改變都不會影響祖先,除非通知祖先更新其 state
state 與 props
state 是最小可變狀態集,特點:
-
私有的。由元件自身完全控制,而不是來自上方
-
可變的。會隨時間變化
-
獨立存在。無法透過其他
state或者props計算出來
props 是不可變的,僅用來填充視圖範本:
props React Element描述对象
-----> 组件 ---------------------> 视图
資料繫結?
2 個環節
-
相依性收集(靜態相依/動態相依)
-
監聽變化
首次渲染時收集 data-view 的映射關係,後續確認資料變化後,更新資料對應的視圖
3 種實作方式
| 實作方式 | 相依性收集 | 監聽變化 | 案例 |
|---|---|---|---|
| getter & setter | getter | setter 監聽變化 | Vue |
| 提供資料模型 | 解析範本 | 所有資料操作都走框架 API,通知變化 | Ember |
| 髒檢查 | 解析範本 | 在合適的時機,取最新的值和上次的比較,檢查變化 | Angular |
| 虛擬 DOM diff | 幾乎不收集 | setState 通知變化 | React |
從相依性收集的粒度來看:
-
Vue 透過
getter動態收集相依性粒度最細,最精確 -
Ember 和 Angular 都是透過靜態範本解析來找出相依性
-
React 最粗枝大葉,幾乎不收集相依性,整個子樹重新渲染
state 變化時,重新計算對應子樹的內部狀態,對比找出變化(diff),然後在合適的時機應用這些變化(patch)
細粒度的相依性收集是精確 DOM 更新的基礎(哪些資料影響哪個元素的哪個屬性),無需做額外的猜測和判斷
虛擬 DOM diff 演算法
React 不收集相依性,只有 2 個已知條件:
-
這個
state屬於哪個元件 -
這個
state變化只會影響對應子樹
子樹範圍對於最終視圖更新需要的 DOM 操作而言太大了,需要細化(diff)
tree diff
樹的 diff 是個相對複雜(NP)的問題,先考慮一個簡單場景:
A A'
/ \ ? / | \
B C -> G B C
/ \ | | |
D E F D E
diff(treeA, treeA') 結果應該是:
1.insert G before B
2.move E to F
3.remove F
如果要電腦來做的話,增 和 刪 好找,移 的判定就比較複雜了,首先要把樹的相似程度量化(比如加權編輯距離),並確定相似度為多少時,移 比 刪+增 划算(操作步驟更少)
React diff
對虛擬 DOM 子樹做 diff 就面臨這樣的問題,考慮 DOM 操作場景的特點:
-
局部小改動多,大片的改動少(效能考慮,用顯示隱藏來規避)
-
跨層級的移動少,同層節點移動多(比如表格排序)
假設:
-
假設不同類型的元素對應不同子樹(不考慮「向下看子樹結構是否相似」,
移的判斷就沒難度了) -
前後結構都會帶有唯一的
key,作為diff依據,假定同key表示同元素(降低比較成本)
這樣 tree diff 問題就被簡化成了 list diff(字串編輯問題):
-
走訪新的,找出 增/移
-
走訪舊的,找出 刪
本質是一個很弱的字串編輯演算法,所以,即便不考慮 diff 開銷,單從最終的實際 DOM 操作來看,效能也不是最優的(相比手動操作 DOM)
另外,保險起見, React 還提供了 shouldComponentUpdate 鉤子,允許人工干預 diff 過程,避免誤判
狀態管理
狀態共享與傳遞
-
兄弟 -> 兄弟。提升共享狀態,保證自上而下的單向資料流
-
子 -> 父。由父預先傳入 cb(函式 props)
-
? -> 遠房親戚。遠距離通訊很難解決,需要手動接力,要么透過 context 共享
透過提升狀態來共享,能減少孤立狀態,減少 bug 面,但畢竟比較麻煩。元件間遠距離通訊問題沒有好的解決方案
另一個問題是在複雜應用中,狀態變化(setState)散落在各個元件中,邏輯過於分散,存在維護上的問題
Flux
為了解決狀態管理的問題,提出了 Flux 模式,目標是讓資料可預測
基本思路
(state, action) => state
具體做法
-
用顯性資料,不用衍生資料(先宣告後使用,不臨時造資料)
-
分離資料和視圖狀態(把資料層抽出來)
-
避免階層更新帶來的階層影響(M 與 V 之間互相影響,資料流不清楚)
結構
产生action 传递action update state
view交互 -----------> dispatcher -----------> stores --------------> views

特點是 store 比較重,負責根據 action 更新內部 state 及把 state 變化同步到 view
container 與 view
container 其實就是 controller-view:
-
用來控制 view 的 React 元件
-
基本職能是收集來自 store 的訊息,存到自己的 state 裡
-
不含 props 和 UI 邏輯
Redux 的取捨
action 与Flux一样,就是事件,带有type and data(payload)
同样手动dispatch action
---
store 与Flux功能一样,但全局只有1个,实现上是一颗不可变的状态树
分发action,注册listener。每个action经过层层reducer得到新state
---
reducer 与arr.reduce(callback, [initialValue])作用类似
reducer相当于callback,输入当前state和action,输出新state
call new state
action --> store ------> reducers -----------> view
用一棵不可變狀態樹維護整個應用的狀態,無法直接改變,發生變化時,透過 action 和 reducer 建立新的物件
reducer 的概念相當於 node 中間件,或者 gulp 外掛,每個 reducer 負責狀態樹的一小部分,把一系列 reducer 串聯起來(把上一個 reducer 的輸出作為當前 reducer 的輸入),得到最終輸出 state
對比 Flux
-
把 store 數量限定為 1
-
去掉了 dispatcher,把 action 傳遞給所有頂層 reducer,流向相應子樹
-
把根據 action 更新內部 state 的部分獨立出來,分解到各 reducer
能去掉 dispatcher 是因為純函式 reducer 可以隨便組合,不需要額外管理順序
react-redux
Redux 與 React 沒有任何關係,Redux 作為狀態管理層可以配合任何 UI 方案使用,例如 backbone、angular、React 等等
react-redux 用來處理 new state -> view 的部分,也就是說,新 state 有了,怎樣同步視圖?
container
container 是一種特殊的元件,不含視圖邏輯,與 store 關係緊密。從邏輯功能上看就是透過 store.subscribe() 讀取狀態樹的一部分,作為 props 傳遞給下方的普通元件(view)
connect()
一個看起來很神奇的 API,主要做 3 件事:
-
生成 container
-
負責把 dispatch 和 state 資料作為 props 注入下方普通元件
-
內建效能優化,避免不必要的更新(內建 shouldComponentUpdate)
Provider 是怎麼回事?
目的:避免手動逐層傳遞 store
實作:在頂層透過 context 注入 store,讓下方所有元件共享 store
生態
-
除錯工具 DevTools
-
平台 React Native
-
元件庫 antd Material-UI
-
發展 Rax
-
狀態管理層 Redux Saga Dva
暫無評論,快來發表你的看法吧