阅读须知
- 文章参考的preact版本是10.5.13
- 文章会省略大部分逻辑,比如hydrating,context,isSvg等。所以如果有大佬点进来需谨慎。
- 简略版本代码
组件和VNode的转换
- preact类型:
string | ComponentClass | FunctionComponent; (string:"div", "span"等节点)
export function createVNode(type, props, key, ref, original) {
const vnode = {
type,
props,
key,
_children: null,
_parent: null,
_dom: null,
_nextDom: undefined,
_depth: 0,
_component: null,
};
return vnode;
}
render
- 调用:
ReactDOM.render(<Home />, document.body)。Home组件会被webpack转换为vnode。调用diff进行组件生命周期调用,新旧节点对比,替换等操作。
- 注意点:初始会被套上Fragment标签。
export function render (vnode, parentDom) {
const commitQueue = []
const oldVNode = parentDom._children
// 默认都会包裹一层Fragment
vnode = parentDom._children = createElement(Fragment, null, vnode)
diff(
parentDom,
vnode,
oldVNode || EMPTY_OBJ,
commitQueue,
oldVNode ? oldVNode._dom : parentDom.firstChild,
)
commitRoot(commitQueue)
}
diff 三部曲
- diff,diffChildren,diffElementNodes的调用关系
- 每一个vNode都是由diff开始,完成生命周期执行。在diffElementNodes完成dom的创建或者复用,props比较。在diffChildren中完成dom的插入。

例子
class Child extends React.Component {
render() {
return (
<span>hi</span>
)
}
}
class Home extends React.Component {
render() {
return (
<div>
<h1>hello</h1>
<Child />
</div>
)
}
}
ReactDOM.render(<Home />, document.body);

注意点
- parentDom,excessDomChildren, oldDom参数。这几个只对nodes起作用,组件只做传递作用。 也就是会在diffElementNodes修改,diff,diffChildren只做传递。
- parentDom:当前nodes的父节点。diffElementNodes对比真实nodes之后,传递的parentDom就会变为当前节点。因为子noeds的父节点是当前节点。
- excessDomChildren: 当前nodes的子节点,只有真实nodes才会有子节点,diffElementNodes对比真实nodes之后会重新赋值。
- oldDom:大部分情况都是oldChildren[0]._dom, 也就是旧节点的第一个真实子节点。
- dom参数:diff调用diffElementNodes,真实的旧节点,oldVNodes._dom。
commitRoot
- 首次渲染时,子组件的compomentDidMount要比父组件的compomentDidMount先执行,componentDidUpdate是要等到render之后才执行。preact做法是将compomentDidMount和componentDidUpdate保存在组件_renderCallbacks本身的数组中。
- this.setState的callback回调函数也是放在_renderCallbacks中
export function commitRoot(commitQueue, root) {
commitQueue.some(c => {
try {
commitQueue = c._renderCallbacks
c._renderCallbacks = []
commitQueue.some(cb => {
cb.call(c)
})
} catch (e) {
options._catchError(e, c._vnode)
}
})
}
- 用一开始的render函数中往下传递commitQueue数组,根据渲染顺序push所有的组件,这样深度比较深的组件会在数组的前面,渲染完之后再去执行对应的生命周期。