Omi架構與React Fiber

原文連接-https://github.com/AlloyTeam/omi/tree/master/tutorialhtml

寫在前面

Omi框架在架構設計的時候就決定把update的控制權交給了開發者,視靈活性比生命還重要。否則的話,若是遇到React Fiber要解決的這類問題的話,就須要推翻原有架構從新搞了。node

React Fiber

先引用下咱們團隊小鮮肉Stark偉-復旦大四 / 騰訊@AlloyTeam知乎上的回答react

React 的核心思想是每次對於界面 state 的改動,都會從新渲染整個 virtual dom,而後新老的兩個 virtual dom 樹進行 diff,對比出變化的地方,而後經過 renderer 渲染到實際的UI界面(這裏多是瀏覽器的DOM,也多是native組件)。這樣實質上就是把界面變成一個純粹的狀態機,React 的做用就是把這個狀態機之間的狀態轉換高效率地運行出來。可是存在如下問題:git

  • 一、不是每一次狀態的變化都要馬上執行。
  • 二、不一樣的狀態變化之間是有輕重緩急之分的,好比『動畫』這種狀態變化的優先級,出於對用戶體驗的考量,爲了不動畫卡頓或者掉幀,通常比『改變頁面數據』的優先級更高。
  • 三、咱們如今的作法只是調用 setState 觸發從新渲染,而後 React 會收集一個 tick 內的 state 變化,而後執行,因此有可能大量的計算會在同一時刻阻塞進程。但咱們無法控制 React 運算的時序問題,也不太可能經過手工聲明讓動畫的優先級比數據變動更高。而 React 做爲一個用戶交互的框架,它本應該能讓程序員能控制這些東西。因此這個破事要怎麼解決咧?( ⊙ o ⊙ )咱們知道,任何的函數調用都會有本身的調用棧,好比對於 v = f(d) 這個函數來講,函數 f 可能又調用了一系列其它的函數,這些函數就包括在 f 的調用棧中。關鍵的問題在於,這種原生的調用棧是基本不可延遲的,它會當即執行,若是計算量很大的話就會阻塞住進程,讓界面失去響應,這種事情常常發生在 React 的渲染過程當中。

或者看顏什麼都不記得適的回答程序員

狀態轉移時,是在一次 tick 中遞歸遍歷組件樹,找出須要更新的節點 rerender。可是這樣形成了一些問題:github

  • 在 UI 中,不是全部的狀態轉移都須要當即執行。大量的同時計算可能會致使資源的浪費,以至出現掉幀的情況,下降用戶體驗。
  • 不一樣類型的狀態轉移應有不一樣的優先級,好比點擊按鈕出現動畫的優先級應該比 Fetch API 要高。
  • React 是 pull-based 實現的,事務的時序所有由 React 決定。咱們沒辦法操控執行事務的時序。

Omi component update

Omi有上面的問題嗎? 沒有。web

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相關

相關文章
相關標籤/搜索