初始化 Component
流程解析
外部引用方式
通过
createApp的mount方法进行挂载返回APP实例
import { crerateApp } from "../../lib/guide-mini-vue.esm.js"
// import App from "./App.js"
import { App } from "./App.js"
const rootComponent = document.querySelector("#app")
crerateApp(App).mount(rootComponent)
- 创建 App 组件
- 直接使用 render 函数返回虚拟节点来渲染
- 通过 h 函数进行将真实DOM转换为虚拟DOM
- setup返回当前组件的数据
import { h } from "../../lib/guide-mini-vue.esm.js";
window.self = null;
export const App = {
render() {
window.self = this;
return h(
"div",
{
id: "123",
class: ["lbxin", "lbxin-active"],
},
"name " + this.msg + " age:" + this.age
//Proxy代理this.setup返回的值 代理this.$el(根实例 - root element)的值
// [h("p",{class:'red'},'hi'),h("span",{class:"blue"},"Lbxin")]
);
},
setup() {
// 返回当前组件的数据
return {
msg: "Lbxin",
age: 12,
};
},
};
// const HelloWorld = {
// render() {
// return h("div", "Lbxin " + this.msg);
// },
// setup() {
// return {
// msg: "min-vue",
// };
// },
// };
// export default {
// name: "App",
// setup() {},
// render() {
// return h("div", { tId: 1 }, [h("p", {}, "主页"), h(HelloWorld)]);
// },
// };
实现主流程
- crerateApp 返回 mount 进行实例化组件
- mount 内部实现
- 先将真实DOM节点转换为虚拟DOM,后续所有内部实现都是基于当前的虚拟DOM实例进行操作的
- 参数是外部的真实DOM
- 转换后使用虚拟DOM进行渲染
- mount 内部实现
import { render } from "./render";
import { createVNode } from "./vnode";
export function crerateApp(rootComponent) {
return {
mount: function (rootContainer) {
// 先转换为虚拟节点vNode,后续所有操作都是基于该虚拟节点进行操作的
// 接受的参数是一个节点组件,所以需要先将组件转换为虚拟节点 Component → vNode
const vNode = createVNode(rootComponent);
//直接使用VNode虚拟节点进行渲染
render(vNode, rootContainer);
},
};
}
- h 函数 真实DOM转换为虚拟DOM
import { createVNode } from "./vnode";
export function h(type, props?, children?) {
// 用来将DOM的描述信息转换为虚拟DOM
return createVNode(type, props, children);
}
- createVNode 将组件信息组合成一个对象(虚拟DOM)并返回
export function createVNode(type, props?, children?) {
// 将组件信息组合为一个对象返回 虚拟DOM
const vNode = {
type,
props,
children,
el: null,
};
return vNode;
}
- render 调用
patch方法,方便后续递归处理
export function render(vnode, container) {
// 调用patch函数 方便进行后续递归处理
patch(vnode, container);
}
- patch 处理组件 组件分为封装的组件和普通DOM元素
function patch(vnode, container) {
// 处理组件 分为普通节点和封装的组件
if (typeof vnode.type === "string") {
processElement(vnode, container);
} else if (isObject(vnode.type)) {
processComponent(vnode, container); //挂载组件
}
}
- processComponent 处理组件 执行转换挂载操作
function processComponent(vnode: any, container: any) {
// 挂载组件
mountComponent(vnode, container);
}
- mountComponent 通过虚拟节点创建组件实例
- 通过虚拟节点创建组件实例,并将原生传入的属性挂载到该组件实例上
- createComponentInstance 创建组件实例,并挂载相应的属性
function mountComponent(initialVNode: any, container) { // 通过虚拟节点创建组件实例 const insatnce = createComponentInstance(initialVNode); const { data } = insatnce.type // 通过data函数获取原始数据,并调用reactive函数将其包装成响应式数据 // const state = reactive(data()) // 为了使得自身状态值发生变化时组件可以实现更新操作,需要将整个渲染任务放入到Effect中进行收集 effect(() => { setupComponent(insatnce); //处理setup的信息 初始化props 初始化Slots等 setupRenderEffect(insatnce, initialVNode, container); // 首次调用App组件时会执行 并将render函数的this绑定为创建的代理对象 }) } export function createComponentInstance(vnode) { const component = { vnode, type: vnode.type, render: vnode.render, setupState: {}, }; return component; }- setupComponent(insatnce) 处理setup的信息 初始化props 初始化Slots等
- 通过Proxy进行代理 setup中的数据 从而使得内部的this可以访问到setup中的数据 - 拦截this
- 当访问Proxy时 会先访问到组件实例中获取组件实例的setupState属性 即setup函数返回的值,可以通过此进行判断
- setup可以返回函数或对象 函数-是组件的render函数 对象-将对象返回的对象注入到这个组件上下文中 setup返回当前组件的数据
- handleSetupResult 将setup中的数据添加到实例上
- finishComponentSetup 将组件对象上的render添加到实例对象上
- 通过Proxy进行代理 setup中的数据 从而使得内部的this可以访问到setup中的数据 - 拦截this
export function setupComponent(instance) {
// 处理setup的信息 初始化props 初始化Slots等
// initProps()
// initSlots()
setupStatefulComponent(instance);
}
function setupStatefulComponent(instance: any) {
// 调用组件的setup
// 首次调用patch时 instance.type就是传入的App组件
// const Component = instance.vNode.type
const Component = instance.type;
instance.proxy = new Proxy(
{ _: instance },
PublicInstanceProxyHandlers
// {
// get(target,key){
// const { setupState } = instance
// if(key in setupState){
// return setupState[key]
// }
// if(key === '$el'){
// return instance.vnode.el
// }
// }
// }
);
const { setup } = Component;
if (setup) {
// setup可以返回函数或对象 函数-是组件的render函数 对象-将对象返回的对象注入到这个组件上下文中
const setupResult = setup();
// setup返回当前组件的数据
handleSetupResult(instance, setupResult);
}
}
function handleSetupResult(instance, setupResult: any) {
// fuanction object
// 将setup中的数据添加到实例上
if (typeof setupResult === "object") {
instance.setupState = setupResult;
}
finishComponentSetup(instance);
}
function finishComponentSetup(instance: any) {
// 将组件对象上的render添加到实例对象上
const Component = instance.type;
if (Component.render) {
instance.render = Component.render;
}
}
// 在组件中通过 `this.$el` 可以访问到组件对应的真实DOM。
const publicPropertiesMap = {
$el: (i) => i.vnode.el,
};
export const PublicInstanceProxyHandlers = {
get({ _: instance }, key) {
const { setupState } = instance;
if (key in setupState) {
return setupState[key];
}
// if(key === '$el'){
// return instance.vnode.el
// }
const publicGetter = publicPropertiesMap[key];
if (publicGetter) {
return publicGetter(instance);
}
},
};
注意:mountElement是比setupStatefulComponent后执行,setupStatefulComponent执行的时候,vnode.el不存在,后续mountelement的时候,vnode才会有值,所以直接在mountElement中挂载$el是有问题的,需要在render函数执行后通过执行结果将el挂在到虚拟节点上,而setupRenderEffect在首次调用App组件时就会执行
function setupRenderEffect(
// state,
// insatnce: {
// vnode: any;
// type: any; // 调用patch函数
// render: any
// },
insatnce: any,
initialVNode,
container
) {
// 根据VNode获取组件的选项对象
// const subTree = insatnce.vnode.render.call(state,state);
const { proxy } = insatnce
//指定instance中的this到当前节点 统一this 而非外部变化的实例等指向
const subTree = insatnce.render.call(proxy);
// 通过render获取到组件需要渲染的内容 即render函数返回的虚拟DOM
// 通过调用patch函数来挂载组件所需要描述的内容 即subTree
patch(subTree, container);
// 所有的 subTree 都初始化结束
initialVNode.el = subTree.el // 存储根节点到VNode中 方便后续获取
}
- setupRenderEffect 将render函数的this绑定为创建的代理对象 首次渲染会默认执行
function setupRenderEffect(
// state,
// insatnce: {
// vnode: any;
// type: any; // 调用patch函数
// render: any
// },
insatnce: any,
initialVNode,
container
) {
// 根据VNode获取组件的选项对象
// const subTree = insatnce.vnode.render.call(state,state);
const { proxy } = insatnce
//指定instance中的this到当前节点 统一this 而非外部变化的实例等指向
// App组件中的render方法中的this指的是App组件 需要改到组件实例的proxy属性
// instance.render 来自于 finishComponentSetup 方法,就是组件的render方法
// 让render中的this指向创建的代理对象
const subTree = insatnce.render.call(proxy);
// 通过render获取到组件需要渲染的内容 即render函数返回的虚拟DOM
// 通过调用patch函数来挂载组件所需要描述的内容 即subTree
// subTree指的就是class="root"的根节点
patch(subTree, container);
// 所有的 subTree 都初始化结束
// 后续就可以通过this.$el访问到虚拟DOM对应的真实DOM了
initialVNode.el = subTree.el // 存储根节点到VNode中 方便后续获取
}
初始化 element
流程解析
内部实现
- patch 打补丁时进行判断指定处理
function patch(vnode, container) {
// 处理组件 分为普通节点和封装的组件
if (typeof vnode.type === "string") {
processElement(vnode, container);
} else if (isObject(vnode.type)) {
processComponent(vnode, container); //挂载组件
}
}
- mountElement 将虚拟DOM挂载到对应的容器上
function processElement(vnode: any, container: any) {
mountElement(vnode, container)
}
function mountElement(vnode: any, container: any) {
// vnode → element → div
const { children, props, type } = vnode
// 创建对应的DOM,同时绑定到虚拟DOM上
// 创建真实DOM时需要将真实DOM也挂载到虚拟DOM的el上 后续进行$el拦截时需要返回真实DOM的实例
const el = (vnode.el = document.createElement(type))
if (typeof children === "string") {
// 普通文本 直接赋值给textContent
el.textContent = children
} else if (Array.isArray(children)) {
// 多个子元素 需要循环递归patch
mountChildren(vnode, container)
}
for (const key in props) {
if (Object.prototype.hasOwnProperty.call(props, key)) {
const val = props[key];
el.setAttribute(key, val)
}
}
container.append(el)
}
function mountChildren(vnode: any, container: any) {
vnode.children.forEach(element => {
patch(element, container)
});
}