初始化阶段
首先调用createApp创建App,然后内部会返回mount函数
import { render } from "./renderer";
import { createVnode } from "./vnode";
export function createApp(rootComponent) {
...
return {
mount(rootContainer) {
// 首先会将所有渲染相关的转换成vnode 虚拟节点
// 后续所有逻辑操作 都会基于vnode做处理
const vnode = createVnode(rootContainer);
render(rootContainer, vnode);
},
};
}
mount是用来将组件挂载到具体DOM上
会将用户传进来的数据转换成虚拟node,后面所有逻辑操作,都会在vnode上做处理
export function createVnode(type, props?, children?) {
return {
type,
props,
children,
};
}
然后接下来会调用render函数做处理
无论是使用模板编写还是使用setup方式编写最终都会执行render函数
import { createComponentInstance } from "./component";
function render(vnode, container) {
//调用patch方法
patch(vnode, container);
}
/**
* 用来做DOM更新
* @param vnode
* @param container
*/
function patch(vnode, container) {
if (typeof vnode.type === "string") {
processElement(vnode, container);
} else {
processComponent(vnode, container);
}
}
在调用patch函数后通过对vnode.type进行判断如果是string类型便是做Element元素处理渲染如果是object那便是做组件处理调用processComponent
component组件初始化
在调用patch函数后通过对vnode.type进行判断如果类型是object类型便做component处理
/**
*
* @param vnode
* @param container
*/
function processComponent(vnode, container) {
mountComponent(vnode, container);
}
/**
* 挂载组件
* @param vnode
* @param container
*/
function mountComponent(vnode, container) {
//对组件实例化
const instance = createComponentInstance(vnode);
setupComponent(instance);
setupRenderEffect(instance, container);
}
mountComponent主要创建一个组件实例,然后调用setupComponent做初始化处理props、slots、setup处理
function setupComponent(instance) {
// TODO
// props
// slots
setupStatefulComponent(instance);
}
setupStatefulComponent中是对setup内部是否存在render函数做处理
function setupStatefulComponent(instance) {
const component = instance.type;
const { setup } = component;
if (setup) {
const setupResult = setup();
handleSetupResult(instance, setupResult);
}
}
function handleSetupResult(instance, setupResult) {
if (typeof setupResult === "object") {
instance.setupState = setupResult;
}
finishComponentSetup(instance);
}
function finishComponentSetup(instance) {
const component = instance.type;
if (component.render) {
component.render();
}
}
最后在`setupRenderEffect`对vnode重新调用`patch`进行节点判断做处理
```typescript
function setupRenderEffect(instance, container) {
const subTree = instance.render();
patch(subTree, container);
}
element初始化
在调用
patch函数后通过对vnode.type进行判断如果类型是string类型便做element处理
const App = {
render() {
return h(
"div",
{
id: "root",
class: ["red", "hard"],
},
[h("p", { class: "red" }, "hi"), h("p", { class: "blue" }, "vue")]
);
},
setup() {
return {
mini: "vue",
};
},
};
这是用户传入App的例子
mountElement
/**
*
* @param vnode
* @param container
*/
function processElement(vnode, container) {
mountElement(vnode, container);
}
/**
* 挂载元素
* @param vnode
* @param container
*/
function mountElement(vnode, container) {
const { props, children, type } = vnode;
const el: HTMLElement = document.createElement(type);
for (let attr in props) {
const attrValue = props[attr];
el.setAttribute(attr, attrValue);
}
mountChildren(children, el);
container.append(el);
}
function mountChildren(children, el: HTMLElement) {
if (typeof children === "string") {
el.textContent = children;
} else if (Array.isArray(children)) {
children.forEach((child) => patch(child, el));
}
}
在processElement的内部调用mountElement对用户传入的渲染数据进行处理,属性很好处理,对于children需要注意两种情况是普通字符串还是用户再一次调用渲染函数返回的vnode对象
[h("p", { class: "red" }, "hi"), h("p", { class: "blue" }, "mini-vue")]
至此渲染element流程工作就做完了。