# React 實踐揭祕之旅,中高級前端必備(下)

引言

上一篇文章咱們主要實現了 JSXWebGL 上的渲染與更新,對 虛擬DOMDiff 有了更深的瞭解,但相比於咱們使用的 React,還缺少了之中很重要的一環 --- 組件模式前端

想必你們能認同,React組件(Component)具備 強大的功能,高拓展性和高解耦性,在其基礎上構建的各類 UI 組件框架徹底改變了傳統的 Web 開發模式,成爲了Web 中的大型複雜應用提供了一種很好的構建模式和保障,也讓咱們的開發效率也有了質的變化。node

在這篇文章中,咱們會在上一篇的實現基礎上,加入 React 組件模式,並在實現的過程當中適時的去講解一些原理和思惟,也有利於你們 由淺入深的理解編程思惟上的提高react

因爲下篇是徹底以上篇做爲基礎的。因此若是你還沒看過上篇,請優先猛戳:git

React 實踐揭祕之旅,中高級前端必備(上) ->github

第七站: 破碎之墟 - Component

代碼複用性,向來是編程領域一個核心的理念。咱們最常使用 函數、類 方式進行代碼的封裝,但這裏有個痛點: web

Web 中 UI 與 邏輯 的分離的特性,致使較難優雅地整合封裝。編程

一般咱們須要引 JSCSS,再在 HTML 中按規定書寫結構,最後再初始化庫。整個過程十分割裂,且不優雅。數組

React Component 幫咱們解決了這個痛點,即保持了 動態UI邏輯 的分離,又有了全新的整合方式。從而讓 UI 組件變得很是的 高效易用,只須要在結構中以標籤的形式引入便可。瀏覽器

咱們就繼續在上篇文章的基礎上,加入 Component 的特性。基於 JSX 的變量傳遞,咱們只須要實現一個 Component 類,並針對性地去完成組件的 渲染和更新 便可。緩存

TIPs:

因爲組件的加入,這時咱們的 虛擬DOM(VNode) 就包含了兩種類型: 組件節點(compVNode)元素節點(elVNode)。後續都會以此區分,便於講解。

// 組件基類
class Component {
    // 經過保持三份不一樣的時期的快照
    // 有利於對組件的狀態及屬性的管理和追蹤

    // 屬性
    public __prevProps
    public props = {}
    public __nextProps
    
    // 狀態
    public __prevState
    public state = {}
    public __nextState
    
    // 儲存當前組件渲染出的 VNode
    public __vnode
    
    // 儲存 組件節點
    public __component
    
    constructor(props) {
        // 初始化參數
        this.props = props
    }
    public render(): any { return null }
    public __createVNode() {
        // 該方法用於封裝從新執行 render 的邏輯
    
        // state 與 props 狀態變動的邏輯
        this.__prevProps = this.props
        this.__prevState = this.state

        this.props = this.__nextProps
        this.state = this.__nextState

        this.__nextState = this.__nextProps = undefined
        
        // 從新執行 render 生成 VNode
        this.__vnode = this.render()
        
        return this.__vnode
    }
}

有了這個類後,咱們就可使用 React 的方式繼承出 自定義組件 進行渲染:

class App extends Component {
    constructor(props) {
        super(props)
        this.state = {
            content: 'ReactWebGL Component, Hello World',
        }
    }
    public render() {
        return (
            <Container name="parent">
                {this.state.content}
            </Container>
        )
    }
}

render(<App />, game.stage)

因爲咱們以前提過,JSX 是以 變量傳遞 的形式編譯的。所以將 <App /> 轉換成 VNode 後,其 vnode.type 的值即是 App 這個類,並非相似 div 那樣的 字符串標籤名。因此接下來咱們須要實現一個 組件初始化 的函數(Component)。這個函數的主要功能是:

實例化組件並獲取組件 render 方法中返回的 元素節點。

// 渲染組件
function renderComponent(compVNode) {
    const { type: Comp, props } = compVNode
    let instance = compVNode.instance
    
    // 當 instance 已經存在時,則不須要從新建立實例
    if (!instance) {
        // 傳入 props,初始化組件實例
        // 支持 類組件 或者 函數組件
        if (Comp.prototype && Comp.prototype.render) {
            instance = new Comp(props)
        } else {
            instance = new Component(props)
            instance.constructor = Comp
            instance.render = () => instance.constructor(props)
        }
        
        // 初次渲染時
        // 未來的屬性與狀態其實即是與當前值一致
        instance.__nextProps = props
        instance.__nextState = instance.state
    }    
    
    // 調用 render 獲取 VNode
    const vnode = instance.__createVNode()

    // 組件、元素、實例之間保持相互引用,有利於雙向鏈接整棵虛擬樹
    instance.__component = compVNode
    compVNode.instance = instance
    compVNode.vnode = vnode
    vnode.component = compVNode

    return vnode
}

接下來咱們只須要在 createElm 的函數加入: 當傳入的爲 組件節點 時調用函數初始化生成 元素節點,後續只須要繼續原有的邏輯繼續建立,便能正確渲染組件。

function createElm(vnode) {
    // 當爲組件時,初始化組件
    // 從新賦值成 元素節點
    if (typeof vnode.type === 'function') {
        vnode = renderComponent(vnode) 
    }
    
    // 維持原有邏輯
    ...
    
    return vnode.elm
}

保存執行,頁面中已經能正確的渲染出 <App> 組件了。延續以前的邏輯,在完成初次渲染後,接下來就是組件的更新。這就是咱們最常使用的 this.setState了。

第八站: 淨化之匙 - setState

在上一篇文章中,咱們實現了 虛擬DOM 更新函數 diff。參數爲 新舊虛擬節點(oldVNodenewVNode)。因此組件更新的原理也同樣:

獲取組件實例前後渲染的新舊 VNode 再觸發 diff 函數。

在剛纔 Component 的渲染中,咱們已經把 render 生成的 VNode 保存在 this.__vnode 上,這即是初始化時生成的 舊虛擬節點(oldVNode)。因此咱們要作的就是:

setState 中 更新狀態,調用 render 生成 新虛擬節點(newVNode),觸發 diff 更新。

所以咱們在類上新增兩個方法: setState__update:

class Component {
    // 其他方法
    ...
    
    // 更新函數
    public __update = () => {
        // 臨時存儲 舊虛擬節點 (oldVNode)
        const oldVNode = this.__vnode
        
        this.__nextProps = this.props
        if (!this.__nextState) this.__nextState = this.state

        // 從新生成 新虛擬節點(newVNode)
        this.__vnode = this.__createVNode()

        // 調用 diff,更新 VNode
        diff(oldVNode, this.__vnode)
    }
    
    // 更新狀態
    public setState(partialState, callback?) {
        // 合併狀態, 暫存於 即將更新態 中
        if (typeof partialState === 'function') {
            partialState = partialState(this.state)
        }
        this.__nextState = {
            ...this.state,
            ...partialState,
        }

        // 調用更新,並執行回調
        this.__update()
        callback && callback()
    }
}

更新優化

到這裏咱們能夠看出: setState 封裝了 diff 方法。但因爲 diff 的複雜度,性能的優化會是一個咱們須要着重考慮的點。每執行一次 setState,就須要從新生成 newVNode 進行 diff。所以,當組件很是複雜時或者連續更新時,可能會致使 主進程的阻塞,形成頁面的卡死。

這裏咱們須要有兩個優化:

  • setState 異步化,避免阻塞主進程;
  • setState 合併,屢次連續調用會被最終合併成一次;

    • 同一組件 連續屢次更新
    • 父子級連續觸發更新,因爲 父級更新其實已經包含子級的更新,此時若是子級再自我更新一次,則就變成了一種無謂消耗;

爲了這個優化,咱們首先須要一個更新隊列功能:

  • 能夠異步化調用更新
  • 爲組件標註,保證一次循環中單個組件只會更新一次

咱們先來實現個 異步執行隊列:

// 更新異步化,採用屬於微任務的 Promise,兼容性使用 setTimeout
// 這裏使用微任務,能夠保證宏任務的優先執行
// 保證例如 UI渲染 等更爲重要的任務,避免頁面卡頓;
const defer = typeof Promise === 'function' 
    ? Promise.prototype.then.bind(Promise.resolve())
    : setTimeout

// 更新隊列
const updateQueue: any[] = []

// 隊列更新 API
export function enqueueRender(updater) {

    // 將全部 updater 同步推入更新隊列中
    // 爲實例添加一個屬性 __dirty,標識是否處於待更新狀態
    // 初始 和 更新完畢,該值會被置爲 false
    // 推入隊列時,標記爲 true
    if (
        !updater.__dirty && 
        (updater.__dirty = true) && 
        updateQueue.push(updater) === 1
    ) {
        // 異步化沖洗隊列
        // 最終只執行一次沖洗
        defer(flushRenderQueue)
    }
}

// 合併一次循環中屢次 updater
function flushRenderQueue() {
    if (updateQueue.length) {
        // 排序更新隊列
        updateQueue.sort()

        // 循環隊列出棧
        let curUpdater = updateQueue.pop()
        while (curUpdater) {
        
            // 當組件處於 待更新態 時,觸發組件更新
            // 若是該組件已經被更新完畢,則該狀態爲 false
            // 則後續的更新均再也不執行
            if (curUpdater.__dirty) {
                // 調用組件自身的更新函數
                curUpdater.__update()

                // 執行 callback
                flushCallback(curUpdater)
            }
            
            curUpdater = updateQueue.pop()
        }
    }
}

// 執行緩存在 updater.__setStateCallbacks 上的回調
function flushCallback(updater) {
    const callbacks = updater.__setStateCallbacks
    let cbk
    if (callbacks && callbacks.length) {
        while (cbk = callbacks.shift()) cbk.call(updater)
    }   
}

完成這個方法後,咱們來修改下上面的 setState 函數:

class Component {
    // 其他方法
    ...
    
    public setState(partialState = {}, callback?) {
        // 合併狀態, 暫存於 即將更新態 中
        // 處理參數爲函數的形式
        if (typeof partialState === 'function') {
            partialState = partialState(this.state, this.props)
        }
        
        this.__nextState = {
            ...this.state,
            ...partialState,
        }

        // 緩存回調
        callback && this.__setStateCallbacks.push(callback)
        // 把組件自身先推入更新隊列
        enqueueUpdate(this)
    }
}

此時,因爲 更新隊列 爲異步的,所以當屢次連續調用 setState 時,組件的狀態會被 同步合併,待所有完成後,纔會進入更新隊列的沖洗並最終只執行一次組件更新。

React 中組件還有個更新的方法: forceUpdate(callback),該方法的功能實際上是與 this.setState({}, callback) 相同的,惟一有個須要注意的點就是: 觸發的更新不須要通過 shouldComponentUpdate 的判斷,實現只須要加個標識位便可。

優化策略

回到性能優化這個點,從這裏的簡單實現咱們能夠看出: 雖然異步化了更新流程,但本質上仍然沒有解決 複雜的組件 diff 帶來長時間執行阻塞主進程。我記得之前文章有說過: 最有效的性能優化方式就是 異步、任務分割 和 緩存策略。

1. 異步化:

經過把同步的代碼執行變成異步,把串行變成並行,能夠有效提升 執行的時間利用率保證代碼優先級。從這裏能夠延伸出兩種優化方向:

    1. 異步: 如咱們上面所作的優化,這樣能保證主進程的執行優先級,保證頁面渲染或者更主要任務的優先執行,避免卡頓;
    1. 並行: 經過把某些高消耗的操做放到 非主進程 上執行,例如 worker 線程。不過因爲 diff 自己就較爲複雜,還要須要處理好主進程與線程之間的交互,會致使複雜度極高,但也並不是不可行,後續也許是個優化方向。
    • 例如我就在思考在這裏引入 wasm 的可能性,代價與收益好比何,有興趣的童鞋能夠一塊兒探討。

2. 任務分割

將本來會阻塞主進程的 大塊邏輯執行進行拆解,分割成一個個小任務。從而能夠在邏輯中找到合適的時機點 分段執行,即 不會阻塞主進程,又可讓代碼快速高效的執行,最大化利用物理資源。

Facebook 的大神們選擇了這條優化方向,這就是 React 16 新引入的 Fiber 理念的最主要目的。上面咱們實現的 diff 中,有着一個很大的障礙:

一棵完整 虛擬DOM樹 更新,必須一次性更新完成,中間沒法被暫停,也沒法被分割。

Fiber 最主要的功能就是 指針映射,保存上一個更新的組件與下一步須要更新的組件,從而完成 可暫停可重啓。計算進程的運行時間,利用瀏覽器的 requestIdleCallbackrequestAnimationFrame 接口,當有優先級更高的任務時,優先執行,暫停下一個組件的更新。待空閒時再重啓更新。

Fiber 算是一種編程思想,在其它語言中也有許多應用(Ruby Fiber)。核心思想是:

任務拆分和協同,主動把執行權交給主線程,使主線程有時間空擋處理其餘高優先級任務。

但實現複雜度較高,爲了本文便於理解,暫時並無引入。等之後有機會咱們再來一塊兒深挖 Fiber 的實現,也許能成爲更多使用場景的性能優化手段。

子組件更新

在上篇文章中,咱們優先實現了 diffVNode 方法用於更新 元素節點,但組件節點的更新與元素節點的更新是不一樣的。當出現組件嵌套的狀況時,咱們就須要一個新的方法(diffComponent)用於組件節點的更新。

元素節點 不一樣,組件節點之間的更新重要的是重渲染,相似於咱們上面的 setState

複用已建立好的組件實例,根據新的 狀態(state)與 屬性(props) 從新執行 render 生成 元素節點,再遞歸比對

也就是說,咱們須要在 diffVNode 外圍再作一層判斷處理:

function diff(oldVNode, newVNode) {
    if (isSameVNode(oldVNode, newVNode)) {
        if (typeof oldVNode.type === 'function') {
            // 組件節點
            diffComponent(oldVNode, newVNode)
        } else {
            // 元素節點,
            // 直接執行比對
            diffVNode(oldVNode, newVNode)
        }
    } else {
        // 新節點替換舊節點
        ...
    }
}


// 組件比對
function diffComponent(oldCompVNode, newCompVNode) {
    const { instance, vnode: oldVNode, elm } = oldCompVNode
    const { props: nextProps } = newCompVNode

    if (instance && oldVNode) {
        instance.__dirty = false
        
        // 更新狀態和屬性
        instance.__nextProps = nextProps
        if (!instance.__nextState) instance.__nextState = instance.state
        
        // 複用舊組件實例和元素
        newCompVNode.instance = instance
        newCompVNode.elm = elm

        // 使用新屬性、新狀態,舊組件實例
        // 從新生成 新虛擬DOM
        const newVNode = initComponent(newCompVNode)
        
        // 遞歸觸發 diff
        diff(oldVNode, newVNode)
    }
}

第九站: 生命之泉 - Lifecycle

組件有一個至關重要的特徵,即是具備 生命週期。不一樣的函數鉤子對應了組件 從初始化到銷燬 的各個關鍵時間點。主要是爲了讓業務方有能力 插入組件的渲染工做流 中,編寫業務邏輯。咱們先來簡單梳理下最新 React 組件的生命週期:

首次渲染:

  • constructor

    • 即組件的 實例化時機,一般能夠用來設置初始化 state
  • static getDerivedStateFromProps(nextProps, prevState)

    • 在組件的模板渲染中,咱們一般使用的數據爲 stateprops,而 props 由父級傳入,組件自己並沒有法直接修改,所以惟一的常見需求就是: 根據父級傳入的 props 動態修改 state。該生命週期就是爲此而生;
    • 你們可能會有疑問: 該方法爲何爲靜態方法? 而不是常規的實例方法呢?

      • 先確定一點: 使用實例方法確定也是能知足需求的。但這個鉤子比較特殊,它的執行時機是位於 新狀態合併以後,重渲染以前,並且該方法會 侵入更新機制 中。若是在之中作例如修改狀態之類的操做是十分不可控的。當設計爲靜態方法後,函數內部便沒法訪問組件實例,成爲一個 純函數,便能保證更新流程的安全與穩定。
  • render()

    • 根據 stateprops,生成 虛擬DOM
  • componentDidMount()

    • 組件被建立成真實元素並渲染後 被調用,此時可獲取真實的元素狀態,主要用於業務邏輯的執行,例如數據請求,事件綁定等;

更新階段:

  • static getDerivedStateFromProps(nextProps, prevState)
  • shouldComponentUpdate(nextProps, nextState)

    • 上篇文章中的 diff 優化策略中有提到: 爲了減小 無謂的更新消耗,賦予組件一個能夠 主動中斷更新流API。根據參數中的 更新屬性更新狀態,業務方自行判斷是否須要繼續往下執行 diff,從而能有效地提高 更新性能
    • 你們記得 React 中有種組件叫 純組件(PureComponent) 吧,其實這個類繼承於普通的 Component 上封裝的,能夠減小多餘的 render,提高性能。

      • 默認使用 shouldComponentUpdate 函數設定更新條件: 僅當 propsstate 發生改變時,纔會觸發更新。這裏使用了 Object 淺層比對,也就是僅作第一層比對,即 1. key 是否徹底匹配;2. value 是否全等; 因此若是須要超過一層的數據變更,純組件即沒法正確更新了;
      • 這也是爲何 React 提倡使用 不變數據 的原理,能有效地使用淺層比對;
      • 不變數據: 提倡數據不可變,任何修改都須要返回一個新的對象,不能直接修改原對象,這樣能有效提升比對效率,減小無謂性能損耗。
  • render()
  • getSnapshotBeforeUpdate(prevProps, prevState)

    • 替換舊版的 componentWillUpdate,觸發時機點: 在數據狀態已更新,最新 VNode 已生成,但 真實元素還未被更新
    • 能夠用來在 更新以前 從真實元素或狀態中獲取計算一些信息,便於在更新後使用;
  • componentDidUpdate(prevProps, prevState, snapshot)

    • 組件更新完成後調用;
    • 能夠用於 監聽數據變化,使用 setState 時必須加條件,避免無限循環;

卸載階段:

  • componentWillUnmount()

    • 組件即將被銷燬。一般能夠用於 解綁事件清除數據釋放內存 等功能;

咱們也按這樣的目標來在咱們的 Component 上實現這些生命週期,那如何更好的組織生命週期呢?這裏我考慮到的是:

組件做爲元素的容器,生命週期的本質實際上是 其渲染出的元素節點的生命週期

也就是說,關鍵點仍是在於 元素在視圖中的工做流,什麼時候被掛載 - 更新 - 卸載。因此爲了更好的維護性和拓展性,更理想的方式應該是 爲元素節點統一添加生命週期,而不是單獨爲組件,這樣可大大下降複雜度,增長可拓展性。

節點生命週期

那咱們第一步先根據上面須要的生命週期來理一下須要哪些時機:

  • 建立後(create)
  • 掛載後(insert)
  • 更新前(willupdate)
  • 更新後(update)
  • 刪除前(willremove)

原理就很簡單了,只須要在 VNode 工做流中的對應時期調用相應的生命週期函數便可。那咱們如今 VNode 上新增一個屬性 hooks,用於 儲存 對應的生命週期函數:

interface VNode {
    ...
    
    hooks: {
        create?: (vnode) => void
        insert?: (vnode) => void
        willupdate?: (oldVNode, newVNode) => void
        update?: (oldVNode, newVNode) => void
        willremove?: (vnode) => void
    }
}

新增一個觸發的方法(fireVNodeHook):

function fireVNodeHook(vnode, name, ...data) {
    // 根據 生命週期名稱
    // 執行儲存在 VNode 上的對應函數便可
    const { hooks: _hooks } = vnode
    if (_hooks) {
        hook = _hooks[name]
        hook && hook(...data)
    }
}

有了這層基礎方法後,咱們只須要分別在以前所寫的 渲染與更新 流程中的各個函數適時地觸發就好了。

1. create

該時機是在 元素被建立後,但還未被掛載以前。因爲咱們以前將邏輯統一收歸爲 createElm,所以只須要在該函數末尾統一加入觸發便可。

function createElm(vnode) {
    // 建立元素邏輯
    ...
    
    // 觸發 虛擬DOM 上儲存的 鉤子函數
    fireVNodeHook(vnode, 'create', vnode)
    
    return vnode.elm
}

2. insert

元素被掛載到視圖 上的時機。從元素的角度來看,就是被 append 到父級中的時機點。這個時機點比較分散,但也比較好加入,找到咱們使用 Api 中的 append 方法加入,總共有三個地方:

  • render 函數中加入對 根節點 的觸發;
  • createElm 函數中加入對 全部子級 的觸發;
  • diffChildren 列表比對中 新增列表項 的觸發;

3. willupdateupdate

更新以前更新以後,對應的即是咱們的 diff 函數。因爲最終均需走到 diffVNode 中,所以只須要在 diffVNode 開頭和末尾觸發便可。

4. willremove

元素被卸載時,其實與 insert 相似,只須要關注 ApiremoveChild 的調用時機便可。在 diff 列表比對期間,當新列表中不存在時,咱們須要刪除舊列表中的元素,也就是以前寫的業務函數 removeVNodes

組件生命週期

因爲 元素節點 纔是貫穿整棵 虛擬DOM 渲染與更新的關鍵,所以咱們上面先實現的是對 元素節點 的生命週期觸發。可是咱們最終須要是 組件節點 的生命週期。因爲 組件節點元素節點 爲一一對應的 上下層級關係,所以這裏咱們還須要作一層轉接:

把組件節點的生命週期賦值給其生成的元素節點

首先咱們先來爲組件定義上生命週期,並定義一箇中轉對象 __hooks,實現 組件節點週期與元素節點週期的轉換:

class Component {
    public __hooks = {
        // 元素節點 插入時機,觸發 didMount
        insert: () => this.componentDidMount(),

        // 元素節點 更新以前,觸發 getSnapshotBeforeUpdate
        willupdate: (vnode) => {
            this.__snapshot = this.getSnapshotBeforeUpdate(this.__prevProps, this.__prevState)
        },

        // 元素節點 更新以後, 觸發 didUpdate
        update: (oldVNode, vnode) => {
            this.componentDidUpdate(this.__prevProps, this.__prevState, this.__snapshot)
            this.__snapshot = undefined
        },

        // 元素節點 卸載以前, 觸發 willUnmount
        willremove: (vnode) => this.componentWillUnmount(),
    }

    // 默認生命週期函數    
    // getDerivedStateFromProps(nextProps, state)
    public getSnapshotBeforeUpdate(prevProps, prevState) { return undefined }
    public shouldComponentUpdate(nextProps, nextState) { return true }
    public componentDidMount() { }
    public componentDidUpdate(prevProps, prevState, snapshot) { }
    public componentWillUnmount() { }
}

而後咱們只須要在 __createVNode 方法中將 this.__hooks 賦值給生成出的 VNode 便可:

class Component {
    ...
    
    public __createVNode() {
        // ...
        this.__vnode = this.render()
        
        // 賦值給對應的 元素節點,
        // 實現該 元素節點 與 組件 之間生命週期的綁定
        this.__vnode.hooks = this.__hooks
        
        return this.__vnode
    }
}

最後,你們可能發現咱們還有兩個鉤子沒有實現: getDerivedStateFromPropsshouldComponentUpdate,這是由於這兩個生命週期會影響到更新結果,所以須要 深刻到更新流程中,沒法單純的經過 元素節點 的生命週期來實現。

但其實也很簡單,就是在 更新以前,須要根據這兩個函數的返回結果,適當調整下更新邏輯便可:

// Componet 中的 __update 方法
class Component {
    // ...

    public __update = () => {
        // 臨時存儲 舊虛擬節點 (oldVNode)
        const oldVNode = this.__vnode
        
        this.__nextProps = this.props
        if (!this.__nextState) this.__nextState = this.state
        
        // 執行 getDerivedStateFromProps
        // 更新 state 狀態
        const cls = this.constructor
        if (cls.getDerivedStateFromProps) {
            const state = cls.getDerivedStateFromProps(this.__nextProps, this.state)
            if (state) {
                this.__nextState = Object.assign(this.__nextState, state)
            }
        }
        
        // 在 diff 以前調用 shouldComponentUpdate 進行判斷
        // true: 生成新 VNode,繼續 diff
        // false: 清空狀態
        if (this.shouldComponentUpdate(this.props, this.__nextState)) {
            // 從新生成 新虛擬節點(newVNode)
            this.__vnode = this.__createVNode()

            // 調用 diff,更新 VNode
            diff(oldVNode, this.__vnode)
        } else {
            // 清空狀態更新
            this.__nextProps = this.__nextState = undefined
        }
        
        // 剛纔 異步更新隊列 中標識的組件 待更新狀態
        // 在更新完後置爲 false
        this.__dirty = false
    }
}

組件更新還有另一個地方,即 diffComponent,也須要加入上述相似的執行和判斷。完成這部分代碼後,咱們來簡單測試個 DEMO:

    1. <App><BBB txt={this.state.txt} /> 正確渲染
    1. 雙組件 渲染生命週期 與預期一致;
    1. 觸發更新,調用 <App> setState<BBB> 文字元素正確更新;
    1. 雙組件 更新生命週期 與預期一致;

<p style="text-align: center;font-weight: bold;">圖1. 生命週期演示DEMO</p>

最後一站: 旅途之末

在這系列文章中,咱們實現了 React 最核心的部分: JSX、組件、渲染、更新。咱們基於 動手實踐 的方式,按部就班地探討了一些原理與策略,得出一些最佳實踐。相信走完這遍旅程後,你們能對 React 有了更深層次的瞭解,可以給到各位小夥伴啓發與幫助。其實我也同樣,也是在這個旅程中跟你們一塊兒共同窗習,共同成長。

React 實踐揭祕之旅,中高級前端必備(上) ->

還有許多模塊,如 ContextRefsFragment 和 一些全局API,如 cloneElement 等,還有代碼中一些更嚴謹的判斷及邊界狀況的處理,並無在文章中體現,主要是因爲這些部分更多的是純邏輯的擴展,同時也是爲了便於理解。若是童鞋們有興趣,能夠到 github 中查看完整版代碼:

react-webgl.js ->

另外我也想稍微嘮嗑下關於 React-WebGL 這個想法。

近階段我接觸了一些 Web 遊戲的開發,有了一些從前端開發者出發的思考與理解。在遊戲開發領域,傳統的遊戲開發者有着一套與前端領域徹底不一樣的思惟編程模式。隨着 Web 的發展,使他們須要拓展到 Js 的環境中。因此出現了一系列的遊戲引擎庫,本質上是從其它平臺的庫移植過來的。當我從一個前端開發者的角度在進行開發時,其實並非說入門難,學習成本高,而是給個人感受是: 相似用純原生 js 在寫頁面,以爲效率低下。因此這也是 React-WebGL 的出發點,指望能將如今 Web 中更優秀的理念運用到遊戲開發,甚至找到一種更高效的開發模式,提高效率,完善生態。

固然,這僅僅只是一個起點, 遊戲開發 與 界面開發 確實有着許多異同點,如何找到一種更現代化更高效的 Web 遊戲開發模式,這還須要很長的一段旅程。我也一直在思考,一直在摸索,相信能有一些好玩的東西。沒有嘗試,沒有努力,就千萬別在起點就放棄了。有什麼問題,有什麼想法,直接找我一塊兒探討哈。🙃~~

Tips:

看博主寫得這麼辛苦下,跪求點贊、關注、Star!更多文章猛戳 ->

郵箱: 159042708@qq.com 微信/QQ: 159042708

[祝福#感恩#武漢加油##RIP KOBE#]()

相關文章
相關標籤/搜索