Omi架構與React Fiber
2017-03-29 11:28 【當耐特】 閱讀(2468) 評論(4) 收藏 舉報原文鏈接-https://github.com/AlloyTeam/omi/tree/master/tutorial
寫在前面
Omi框架在架構設計的時候就決定把update的控制權交給了開發者,視靈活性比生命還重要。不然的話,如果遇到React Fiber要解決的這類問題的話,就需要推翻原有架構重新搞了。
React Fiber
先引用下我們團隊小鮮肉Stark偉-復旦大四 / 騰訊@AlloyTeam在知乎上的回答
React 的核心思想是每次對于界面 state 的改動,都會重新渲染整個 virtual dom,然后新老的兩個 virtual dom 樹進行 diff,對比出變化的地方,然后通過 renderer 渲染到實際的UI界面(這里可能是瀏覽器的DOM,也可能是native組件)。這樣實質上就是把界面變成一個純粹的狀態機,React 的作用就是把這個狀態機之間的狀態轉換高效率地運行出來。但是存在以下問題:
- 1、不是每一次狀態的變化都要立刻執行。
- 2、不同的狀態變化之間是有輕重緩急之分的,比如『動畫』這種狀態變化的優先級,出于對用戶體驗的考量,為了避免動畫卡頓或者掉幀,一般比『改變頁面數據』的優先級更高。
- 3、我們現在的做法只是調用 setState 觸發重新渲染,然后 React 會收集一個 tick 內的 state 變化,然后執行,所以有可能大量的計算會在同一時刻阻塞進程。但我們沒法控制 React 運算的時序問題,也不太可能通過手工聲明讓動畫的優先級比數據變更更高。而 React 作為一個用戶交互的框架,它本應該能讓程序員能控制這些東西。所以這個破事要怎么解決咧?( ⊙ o ⊙ )我們知道,任何的函數調用都會有自己的調用棧,比如對于 v = f(d) 這個函數來說,函數 f 可能又調用了一系列其它的函數,這些函數就包括在 f 的調用棧中。關鍵的問題在于,這種原生的調用棧是基本不可延遲的,它會立即執行,如果計算量很大的話就會阻塞住進程,讓界面失去響應,這種事情經常發生在 React 的渲染過程中。
或者看顏什么都不記得適的回答:
狀態轉移時,是在一次 tick 中遞歸遍歷組件樹,找出需要更新的節點 rerender。但是這樣造成了一些問題:
- 在 UI 中,不是所有的狀態轉移都需要立即執行。大量的同時計算可能會導致資源的浪費,以致出現掉幀的狀況,降低用戶體驗。
- 不同類型的狀態轉移應有不同的優先級,比如點擊按鈕出現動畫的優先級應該比 Fetch API 要高。
- React 是 pull-based 實現的,事務的時序全部由 React 決定。我們沒辦法操控執行事務的時序。
Omi component update
Omi有上面的問題嗎? 沒有。
Omi的賣點之一便是:更自由的更新,每個組件都有update方法,自由選擇你認為最佳的時機進行更新。這樣設計的一大好處是更加靈活,如果想要自動更新集成個mobx或者obajs便可,進可功退可守護。
數據和視圖雖然是關系密切,但是解耦的設計還是非常必要,這樣可以應付更多的場景。好處:
- 你可以等某個動畫播放完成再進行update
- 你可以控制update順序
- 你可以update前后干一些事情而不需要利用生命周期的鉤子函數(有的時候鉤子函數讓連續的邏輯打得粉碎...)
component update說完了嗎?沒有... Omi不僅僅有component update!還有更加強大的 updateSelf。
Omi component updateSelf
先說下兩者的區別:
- update: 更新組件樹
- updateSelf: 更新組件(不包含任何子節點)
如下圖所示:


標紅的代表會進行更新的節點。
場景模擬
class TestComponent extends Omi.Component {
render () {
return `<div>
<h3>{{title}}</h3>
<List name="list" data="listData" />
</div>`;
}
}
組件結構上面代碼所示:
- 如果調用組件實例的update的話,會更新組件本身以及 List組件
- 如果調用組件實例的updateSelft的話,會更新組件本身,不會更新List組件
比如我們僅僅修改了this.data.title,就可以調用this.updateSelf方法,雖然一般情況下無腦update也能達到同樣的結果,雖然morphdom的DOM diff已經足夠輕量快速,但是一定沒有updateSelf方法快速。上面的例子updateSelf優勢可能不明顯,如果這樣呢:
class TestComponent extends Omi.Component {
render () {
return `<div>
<h3>{{title}}</h3>
<List name="list" data="listData" />
<List name="list" data="listData" />
<Content name="list" data="listData" />
<Slider name="list" data="listData" />
</div>`;
}
}
再或者Content、Slider里面再嵌套了子組件,子組件又嵌套了子組件,如果僅僅只是需要修改title的話,updateSelf優勢就盡顯無疑。
實現細節
這里主要說一說updateSelf的實現細節。主要包含兩點:
- 不重新render的情況下拿到子組件的完整的HTML
- 關閉子組件的DOM diff
進行updateSelf的時候,就算子組件的data發生了變化,也不去改變子組件。因為updateSelf就意思就是更新自身。
所以子組件的HTML不需要使用模板和data生成,只需要component.node.outerHTML就可以了。outerHTML在古老的firefox是不支持的,可以通過創建節點插入然后讀innerHTML進行polyfill。
組件本身的HTML是需要使用模板和data生成,子組件就使用剛剛的outerHTML替換便可。但是問題來了,子組件的DOM diff其實是沒有必要的,雖然morphdom的DOM diff已經足夠輕量快速。但是子組件他們本來就是一模一樣,沒有必要的開銷。所以需要關閉DOM diff~~。然后morphdom沒有ignore相關的配置....
擴展 morphdom
API:
morphdom(node, newNodeHTML, {
ignoreAttr: ['attr1','attr2']
} )
比如上面代表只要標記了attr1或者attr2的就是忽略,當然為了規避錯誤,這里需要嚴格的匹配才會ignore DOM diff。怎么算嚴格的匹配?就是:
- 當同樣的attr的DOM,并且該attr在ignoreAttr里才會ignore DOM diff
Omi Store體系addSelfView
Omi Store體系以前通過addView進行視圖收集,store進行update的時候會調用組件的update。
與此同時,Omi Store體系也新增了addSelfView的API。
- addView 收集該組件視圖,store進行update的時候會調用組件的update
- addSelfView 收集該組件本身的視圖,store進行update的時候會調用組件的updateSelf
當然,store內部會對視圖進行合并,比如addView里面加進去的所有視圖有父子關系的,會把子組件去掉。爺孫關系的會把孫組件去掉。addSelfView收集的組件在addView里已經收集的也去進行合并去重,等等一系列合并優化。
Omi相關
- Omi官網omijs.org
- Omi的Github地址https://github.com/AlloyTeam/omi
- 如果想體驗一下Omi框架,可以訪問 Omi Playground
- 如果想使用Omi框架或者開發完善Omi框架,可以訪問 Omi使用文檔
- 如果你想獲得更佳的閱讀體驗,可以訪問 Docs Website
- 如果你懶得搭建項目腳手架,可以試試 omi-cli
- 如果你有Omi相關的問題可以 New issue
- 如果想更加方便的交流關于Omi的一切可以加入QQ的Omi交流群(256426170)

浙公網安備 33010602011771號