文章首发于个人博客 https://github.com/mcuking/bl...相关代码请查阅 https://github.com/mcuking/bl...node
v = f(props, state)
组件的渲染结果由 render,props,state 共同决定,上一讲只是讨论了 render,本讲开始讨论 props 和 state。react
对于 props, 父组件传递过来, 不可变,由基类 Component 设置 props。git
class Component { constructor(props) { this.props = props } }
对于 state, 在组件的生命期内是能够修改的,当调用组件的 setState 方法的时候, 其实就是从新渲染,用一个新 DOM 树替换老的 DOM:github
parent.replaceChild (newdom, olddom)
所以咱们须要解决两个问题:app
这 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 的引用链
相关文章