文章首发于我的博客 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)
因此我们需要解决两个问题:
- 组件实例必须有机制获取到 olddom
- 组件实例必须有机制获取到 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)
}
}
完整功能如下:
//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 的引用链
相关文章
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。