这是我参与11月更文挑战的第4天
createApp() 创建一个应用app
返回一个对象
{
component: function(){},
config: {},
directive: function(){},
mixin : function(){},
mount: function(){},
provide: function(){},
unmount: function(){},
use:function(){},
version: 3.2.22,
_component : {
data: function(){},
methods: {}
},
_container : null,
_context : {},
_instance: null,
_props: null,
_uid: 0
}
normalizeContainer 格式化挂载容器
app._component.template 初始化组件模板
app._component.template = container.innerHTML
mount(rootContainer) 根组件挂载到根容器中
createVnode(rootComponent) 创建根组件的虚拟dom
function _createVnode(app._component){
// 0
// 1: 字符串 element
// 2: 函数 functional_component
// 4: 对象 stateful_component
// 64: teleport
// 128: suspense
const shapeFlag = 4
const vnode = {
el: null,
key: null,
component: null,
children: null,
props: null,
ref: null,
...
patchFlag: 0,
shapeFlag: 4,
type: {
date: {},
methods: {
clickFun: function(){}
},
template: "\n <div @click=\"clickFun\">{{ name }}</div>\n <div>1111</div>\n "
},
appContext = app._context
}
}
render(vnode, rootContainer) 将虚拟dom渲染根容器
根组件的虚拟dom渲染到根容器中的过程
patch(app._container._vnode || null, vnode, container){
// 判断根组件虚拟dom的 vnode.shapeFlag
// 根组件是一个组件 component
// 处理组件
// 每一个createApp()返回的应用都是一个组件,则前面的过程都是一样的
// 从这里开始是核心的处理过程
processComponent(n1,n2, container)
}
处理组件 mountComponent
如果是更新,则执行updateComponent() 如果是第一次,则执行mountComponent
function mountComponent(vnode, container){
// 创建组件实例
let instance = {
type: {methods: {…}, template: '\n <div @click="clickFun">{{ name }}</div>\n <div>1111</div>\n ', data: ƒ}
setupComponent(instance)
}
}
安装组件setupComponent
function setupComponent(){
const {props, children } = instance.vnode
// 初始化props
initProps(instance, props)
initSlots(instance, children)
setupStatefulComponent(instance)
}
安装有状态的组件 setupStatefulComponent
function setupStatefulComponent(){
const Component = instance.type;
Component = {methods: {…}, template: '\n <div @click="clickFun">{{ name }}</div>\n <div>1111</div>\n ', data: ƒ}
if(Component.name){}
if(Component.components){} // 处理子组件
if(Component.directives){} // 处理指令
instance.accessCache = object.create(null)
// 代理组件实例的一些数据
instance.proxy = markRaw(new Proxy(instance.ctx))
// 如果组件有setup函数,则执行
const { setup } = Component
if(setup){
}
finishComponentSetup(instance)
}
完成组件的安装finishComponentSetup
function finishComponentSetup(instance){
const Component = instance.type
if(!instance.render){
const template = Component.template
if(template){
// 将模板字符串编译为render()函数
Component.render = compile(template)
}
}
}
核心第一步:模板字符串编译为render()函数
缓存编译结果,有缓存直接使用缓存
const compileCache = object.create(null)
function compileToFunction(template, options){
// 如果编译过,则直接从缓存中取出来
// 即只有在第一次渲染页面时才编译
const key = template
const cached = compileCache[key]
if(cached){
return cached
}
}
编译第一步:编译生成ast
// template = '\n <div @click="clickFun">{{ name }}</div>\n <div>1111</div>\n '
function baseParse(template){
}
const ast = baseParse(template)
ast = {
loc: {
start:
end:
source:
},
cached: 0,
children: [],
codegenNode: undefined,
components: [],
directives:[],
helpers: [],
hoists: [],
imports: [],
temps: 0,
type: 0
}
编译第二步 :优化/翻译ast
transform(ast){
}
// 此时ast有一些变化
ast.codegenNode = {
}
// 变量提升
ast.hoists = [
{
constType: 0,
content: "[\"onclick\"]",
isStatic: false,
loc:
type: 4
},
{
}
]
编译第三步:生成render函数,generate
经过上面编译三步后生成的render函数如下
是通过new Function()将方法字符串生成函数
const render = (new Function(code)() );
compileCache[key] = render
<div id="app">
<div @click="clickFun">{{ name }}</div>
<div>1111</div>
</div>
(function anonymous(
) {
const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
const _hoisted_1 = ["onClick"]
const _hoisted_2 = /*#__PURE__*/_createElementVNode("div", null, "1111", -1 /* HOISTED */)
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock(_Fragment, null, [
_createElementVNode("div", { onClick: clickFun }, _toDisplayString(name), 9 /* TEXT, PROPS */, _hoisted_1),
_hoisted_2
], 64 /* STABLE_FRAGMENT */))
}
}
})
setupRenderEffect 创建一个render副作用(effect),并传入一个函数
Vue通过一个副作用(effect)来跟踪当前正在运行的函数。
副作用是一个函数的包裹器, 在函数被调用之前就启动跟踪。
Vue知道那个副作用在何时运行,并能在需要时再次执行它。
setupRenderEffect(instance){
// 回调函数
const componentUpdateFn = () => {
}
// 创建一个render
const effect = new ReactiveEffect(componentUpdateFn)
const update = (instance.update = effect.run.bind(effect))
update()
}
ReactiveEffect
class ReactiveEffect {
constructor(fn, scheduler, scope){
this.fn = fn
this.scheduler = scheduler
this.active = true
this.deps = []
}
run(){
this.fn()
}
}
执行render副作用包裹的回调函数
function componentUpdateFn(){
// 执行render函数,获取虚拟dom
render.call()
// 开始patch
}