ZEROMAKE | keep codeing and thinking!
2017-07-30 | source

preact 执行流程

前言

  • @ToPeasIssue来描述下 preact 的流程。
  • 这里决定使用我自己改过的zreact的 flow 分支,这个分支的代码不会再变。

代码例子

这次用个简单的例子, 这里为了简化就不使用 jsx 转换了,手写 jsx。

1
import { h, Component, render } from "zreact";
2
class App extends Component {
3
constructor(props, context) {
4
super(props, context);
5
this.state = {
6
num: 0
7
};
8
}
9
click() {
10
this.setState({ num: this.state.num + 1 });
11
}
12
render(props, state) {
13
return h("div", { onClick: this.click.bind(this) }, state.num);
14
}
15
}
16
render(h(App), document.body);

例子代码分解

  • App 类没什么好说的。
  • render(h(App), document.body),这里的h(App)作为参数会先执行得到
1
VNode:{
2
nodeName: App,
3
children: [],
4
attributes: undefined,
5
key: undefined
6
}
  • 最后render接受到两个参数,一个上面的VNode实例,一个要挂载的父节点。

render 执行流程

1
const child = {};
2
function render(vnode, parent, merge, domChild) {
3
const pchild = domChild || child;
4
return diff(merge, vnode, {}, false, parent, false, pchild);
5
}
1
function diff(
2
dom: Element | undefined,
3
vnode: VNode | void,
4
context: IKeyValue,
5
mountAll: boolean,
6
parent: any,
7
componentRoot: boolean,
8
child: any
9
) {
10
// 因为是第一次调用设置isSvgMode,hydrating
11
const ret = idiff(dom, vnode, context, mountAll, componentRoot, child);
12
// 生成dom后的操作
13
return ret;
14
}
  • idiff通过分支判断出vnode.vnodeName是一个类,调用buildComponentFromVNode
    diff.ts#L158
1
if (typeof vnodeName === "function") {
2
// 是一个组件,创建或复用组件实例,返回dom
3
return buildComponentFromVNode(dom, vnode, context, mountAll, child);
4
}
1
// 通过缓存组件的方式创建组件实例
2
c = createComponent(vnode.nodeName, props, context);
3
// 设置props,并同步执行render
4
setComponentProps(c, props, SYNC_RENDER, context, mountALL);
  • setComponentProps设置一堆属性,触发生命周期componentWillMount
    component.ts#L84
1
// 同步执行render
2
renderComponent(component, SYNC_RENDER, mountAll);
  • renderComponent判断 render 返回的为 html 元素,递归调用diff,递归层数 2
    component.ts#L237
1
// 当前组件的render函数返回的VNode
2
const rendered: VNode | void = component.render(props, state, context);
3
// rendered {
4
// vnodeName: "div",
5
// children: [0],
6
// attributes: {onClick: fun},
7
// key: undefined
8
// }
9
// ...
10
// 渲染原生组件
11
base = diff(
12
// 原dom
13
cbase,
14
// VNode
15
rendered,
16
context,
17
// 父级或者该原生组件,原dom不存在说明必须触发生命周期
18
mountALL || !isUpdate,
19
// 把组件挂载到缓存dom的父级
20
initialBase && initialBase.parentNode,
21
// 以原生组件这里执行说明是自定义组件的第一个原生组件
22
true,
23
// dom上下文
24
component.child
25
);
1
if (!dom || !isNamedNode(dom, vnodeName)) {
2
// 没有原dom或者原dom与vnode里的不同,新建一个
3
out = createNode(vnodeName, isSvgMode);
4
// ...
5
}
6
// ...
7
const vchildren = vnode.children;
8
else if (vchildren && vchildren.length || fc != null) {
9
if (!child.children) {
10
child.children = [];
11
}
12
// vnode子元素需要渲染或者为空但dom子元素需要清空
13
diffChildren(
14
out,
15
vchildren,
16
context,
17
mountAll,
18
hydrating || props.dangerouslySetInnerHTML != null,
19
child,
20
);
21
}
  • diffChildren只有一个子节点需要渲染,为 1 调用idiff渲染
    diff.ts#L360
1
// vchild=1
2
child = idiff(child && child.base, vchild, context, mountAll, false, tchild);
  • idiff创建 Text
1
out = document.createTextNode(data);
2
return out;
  • 回到diffChildren挂载到父 dom 上结束这个方法回到idiff
    diff.ts#L367
dom.appendChild(child);
  • idiff创建完 dom 与子 dom 后,通过diffAttributes设置事件和原生属性,然后返回到diff
    diff.ts#L233
    diff.ts#L238
1
// 设置dom属性
2
diffAttributes(out, vnode.attributes, props, child);
3
4
return out;
  • diff因为是递归层数 2 且不需要挂载,直接返回到renderComponent
    diff.ts#L96
return ret;
  • renderComponent把 dom 挂载到 Component,一大堆操作后继续返回到setComponentProps
    component.ts#L287
componentRef.base = base;
1
// 获取dom
2
dom = c.base;
3
return dom;
1
parent.appendChild(ret);
2
return ret;
  • render返回到用户代码,到此结束。

render 流程图

render-flow

setState 流程