mini-react 實現原理講解 第二講

文章首發於個人博客 https://github.com/mcuking/bl...

相關代碼請查閱 https://github.com/mcuking/bl...node

v = f(props, state)
組件的渲染結果由 render,props,state 共同決定,上一講只是討論了 render,本講開始討論 props 和 state。react

props

對於 props, 父組件傳遞過來, 不可變,由基類 Component 設置 props。git

class Component {
    constructor(props) {
        this.props = props
    }
}

state

對於 state, 在組件的生命期內是能夠修改的,當調用組件的 setState 方法的時候, 其實就是從新渲染,用一個新 DOM 樹替換老的 DOM:github

parent.replaceChild (newdom, olddom)

所以咱們須要解決兩個問題:app

  1. 組件實例必須有機制獲取到 olddom
  2. 組件實例必須有機制獲取到 parentDOM

這 2 個問題實際上是一個問題。 parent = olddom.parentNode, 因此上一行代碼等價於:dom

olddom.parentNode.replaceChild (newdom, olddom)

如今的關鍵就是獲取到 olddom,採用的機制是:
將每一個組件實例直接渲染出的組件實例 / DOM 設置 爲該組件實例的 rendered 屬性,造成一個__rendered 鏈
例如上一講的組件嵌套案例的__rendered 鏈以下:this

Animal --__rendered--> Pet --__rendered--> Cat --__rendered--> div

經過如下代碼實現完整的__rendered 鏈,其中 comp 參數表明 "我是被誰渲染的":code

function render (vnode, parent, comp) {
    let dom
    if(typeof vnode == "string") {
        const dom = ...  // 建立文本節點
        comp && (comp.__rendered = dom)
        ...  // other op
    } else if(typeof vnode.nodeName == "string") {
        const dom = ... // 建立 dom 節點
        comp && (comp.__rendered = dom)
        ... // other op
    } else if (typeof vnode.nodeName == "function") {
        const inst = ... // 建立 組件實例
        comp && (comp.__rendered = inst)
        ... // other op
    }
}

當第一次渲染造成了完整的__rendered 鏈後,再次渲染(經過 setState 等)時,便可經過當前渲染的組件實例,沿着__rendered 鏈向下找到實際渲染的 dom 節點,即 olddom。從而得到 parent,即 olddom.parentNode。對象

// 找到當前組件實例渲染的的實際的 DOM 節點
function getDOM(comp) {
    let rendered = comp.__rendered
    // 經過__render 鏈向下找到第一個非組件的 dom 節點
    while (rendered instanceof Component) {
        rendered = rendered.__rendered
    }
    return rendered
}

進而調用 setState,使用 dom 替換 olddom,代碼以下:blog

function render(vnode, parent, comp, olddom) {
    let dom
    if(typeof vnode == "string") {
        ...
        if(olddom) {
            parent.replaceChild(dom, olddom)
        } else {
            parent.appendChild(dom)
        }
        ...
    } else if(typeof vnode.nodeName == "string") {
        ...
        if(olddom) {
            parent.replaceChild(dom, olddom)
        } else {
            parent.appendChild(dom)
        }
        ...
    } else if (typeof vnode.nodeName == "function") {
        ...
        render(innerVnode, parent, inst, olddom)
    }
}

完整功能以下:

//Component
class Component {
    constructor(props) {
        this.props = props
    }

    setState(state) {
        setTimeout(() => {
            this.state = state
            const vnode = this.render()
            let olddom = getDOM(this)
            render(vnode, olddom.parentNode, this, olddom)
        }, 0)
    }
}


function getDOM(comp) {
    let rendered = comp.__rendered
    while (rendered instanceof Component) { // 判斷對象是不是 dom
        rendered = rendered.__rendered
    }
    return rendered
}

//render
function render (vnode, parent, comp, olddom) {
    let dom
    if(typeof vnode == "string" || typeof vnode == "number") {
        dom = document.createTextNode(vnode)
        comp && (comp.__rendered = dom)
        parent.appendChild(dom)

        if(olddom) {
            parent.replaceChild(dom, olddom)
        } else {
            parent.appendChild(dom)
        }
    } else if(typeof vnode.nodeName == "string") {
        dom = document.createElement(vnode.nodeName)

        comp && (comp.__rendered = dom)
        setAttrs(dom, vnode.props)

        if(olddom) {
            parent.replaceChild(dom, olddom)
        } else {
            parent.appendChild(dom)
        }

        for(let i = 0; i < vnode.children.length; i++) {
            render(vnode.children[i], dom, null, null)
        }
    } else if (typeof vnode.nodeName == "function") {
        let func = vnode.nodeName
        let inst = new func(vnode.props)

        comp && (comp.__rendered = inst)

        let innerVnode = inst.render(inst)
        render(innerVnode, parent, inst, olddom)
    }
}

總結

render 方法負責把 vnode 渲染到實際的 DOM, 若是組件渲染的 DOM 已經存在就替換, 而且保持一個完整的 __rendered 的引用鏈

相關文章

相關文章
相關標籤/搜索