首先从我们的main.js入手
// example文件夹 index.html main.js App.js
import {createApp} from '../../lib/guide-mini-vue.esm.js'
import {App} from './App.js'
const rootContainer = document.querySelector('#app')
createApp(App).mount(rootContainer)
App.js
import {h} from '../../lib/guide-mini-vue.esm.js'
export const App = {
name:"App",
render(){
return h('div',{tId:1},[h("p",{},"主页")])
},
setup(){}
}
导入的createApp是利用rollup打包库中的代码生成的lib文件夹中引入的看看我们的createApp函数中做了什么
import { createVNode } from "./vnode";
// render
export function createAppAPI(render){
return function createApp( rootComponent){
return {
// 接受一个根容器
mount(rootContainer){
// 在vue3都会将所有的元素转换成虚拟节点
// 所有的逻辑操作都会基于vnode来执行
const vnode = createVNode(rootComponent);
render(vnode,rootContainer)
}}}}
首先会基于我们传入的App组件创建一个虚拟节点,可以先展示下生成的虚拟节点的结构
Object{
children:undefined,
component:null,
el:null,
key:null,
props:{},
shapeFlags:4,
type:{name:'App',setup:fn,render:fn}
}
这个对象中的type其实就是我们在App传入的结构,最终会通过template编译成我们的渲染函数
h('div',{tId:1},[h("p",{},"主页")])
h函数主要是用来创建我们的虚拟节点,第一个参数是我们的type节点类型,第二个是props,第三个是children
我们可以先了解vnode的创建函数,可以看到我们在vnode上挂载了我们常见的几个重要的属性,其中还涉及到了对我们的shapeFlag的操作
export function createVNode(type,props?,children?){
const vnode = {
type,
props,
children,
component:null,
key:props && props.key,
shapeFlag:getShapeFlag(type),
el:null
}
// children
if(typeof children === 'string'){
vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN
}else if(Array.isArray(children)){
vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN
}
// 如何判定给定的参数是一个slot参数
// 必须是一个组件节点 并且它的children必须是一个Object
if(vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT){
if(typeof children === 'object'){
vnode.shapeFlag |= ShapeFlags.SLOT_CHILDREN
}
}
return vnode
}
进入render其实就是调用改了patch函数,通过从传入对象结构出来的type来根据不同的类型执行不同的流程
function render(vnode,container){
// 构建patch方法 方便后续的递归
patch(null,vnode,container,null,null)
}
// 改为接受 两个虚拟节点 n1 表示之前的虚拟节点 n2表示最新的虚拟节点
function patch(n1,n2,container,parentComponent,anchor){
const {type,shapeFlag} = n2
// 增加一种类型只渲染我们的children
// Fragment => 只渲染我们的children
switch (type) {
case Fragment:
processFragment(n1,n2,container,parentComponent,anchor)
break;
case Text:
processText(n1,n2,container)
break;
default: // vnode => flag 我们当前虚拟节点的类型都称之为我们的flag
// 比如我们的字符串就作为元素来对待
// if(typeof vnode.type === 'string'){
if(shapeFlag & ShapeFlags.ELEMENT ){
// 上面的判断可以使用位运算符来进行替换
// 当虚拟节点的类型是一个字符串时,就作为一个元素节点
processElement(n1,n2,container,parentComponent,anchor)
// isObject(vnode.type) 同样进行替换
}else if(shapeFlag & ShapeFlags.