文章首发于我的博客 https://github.com/mcuking/bl...

相干代码请查阅 https://github.com/mcuking/bl...

v = f(props, state)
组件的渲染后果由 render,props,state 独特决定,上一讲只是探讨了 render,本讲开始探讨 props 和 state。

props

对于 props, 父组件传递过去, 不可变,由基类 Component 设置 props。

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

state

对于 state, 在组件的生命期内是能够批改的,当调用组件的 setState 办法的时候, 其实就是从新渲染,用一个新 DOM 树替换老的 DOM:

parent.replaceChild (newdom, olddom)

因而咱们须要解决两个问题:

  1. 组件实例必须有机制获取到 olddom
  2. 组件实例必须有机制获取到 parentDOM

这 2 个问题其实是一个问题。 parent = olddom.parentNode, 所以上一行代码等价于:

olddom.parentNode.replaceChild (newdom, olddom)

当初的要害就是获取到 olddom,采纳的机制是:
将每个组件实例间接渲染出的组件实例 / DOM 设置 为该组件实例的 rendered 属性,造成一个__rendered 链
例如上一讲的组件嵌套案例的__rendered 链如下:

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

通过以下代码实现残缺的__rendered 链,其中 comp 参数代表 "我是被谁渲染的":

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,代码如下:

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)    }}

残缺性能如下:

//Componentclass 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}//renderfunction 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 的援用链

相干文章

  • mini-react 实现原理解说 第一讲
  • mini-react 实现原理解说 第二讲
  • mini-react 实现原理解说 第三讲
  • mini-react 实现原理解说 第四讲
  • mini-react 实现原理解说 第五讲