组件的实现原理
从用户的层面看来,一个组件就是一个选项对象或者是一个函数, 在patch的时候,
function patch(n1, n2, container, anchor) {
if (typeof type === "string") {
//...
} else if (type === Text) {
//...
} else if (type === Fragment) {
//...
} else if (typeof type === "object" || typeof type === "function") {
// 渲染组件的过程
if (!n1) {
mountComponent(n2, container, anchor);
} else {
patchComponent(n1, n2, anchor);
}
}
}
mountComponent逐行分析
// 当前活跃组件的指针, 执行挂载的组件
let currentInstance = null;
// 挂载组件的方法
function mountComponent(vnode, container, anchor) {
// 是否是函数组件
const isFunctional = typeof vnode.type === 'function'
let componentOptions = vnode.type
// 函数组件的处理过程
if (isFunctional) {
componentOptions = {
render: vnode.type, // 直接将函数 赋值给render函数
props: vnode.type.props
}
}
// 从组件选项中 解构出用户提供的内容
let { render, data, setup, beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, props: propsOption } = componentOptions
// 调用beforeCreate 钩子函数
beforeCreate && beforeCreate()
// 将传入的data响应化
const state = data ? reactive(data()) : null
// 将 vnode中的 props 和 组件上选项props进行整合
const [props, attrs] = resolveProps(propsOption, vnode.props)
// 获取solt
const slots = vnode.children || {}
// 组件实例本质上就是一个状态集合(或一个对象), 它维护着组件运行过程中的所有信息、如注册到组件的生命周期函数、组件渲染的子树(su ubTree)、组件是否已经被挂载、组件自身的状态(data)等
const instance = {
state,
props: shallowReactive(props),
isMounted: false, //是否被挂载
subTree: null,
slots,
mounted: []
// ...
}
// 传递给stepup()函数的emit,就是它 , emit('change', '001');
function emit(event, ...payload) {
// 先把change 转换成 onChange
const eventName = `on${event[0].toUpperCase() + event.slice(1)}`
// 调用 当前组件上 注入的 props.onChange 事件, 由子组件出发
const handler = instance.props[eventName]
if (handler) {
handler(...payload)
} else {
console.error('事件不存在')
}
}
// setup函数
let setupState = null
if (setup) {
// setup函数的入参
const setupContext = { attrs, emit, slots }
// 设置当前组件
const prevInstance = setCurrentInstance(instance)
// 执行setup函数, 并收集结果, setup函数中的 props是只读的
const setupResult = setup(shallowReadonly(instance.props), setupContext)
setCurrentInstance(prevInstance)
// setup函数的两种情况: 1-可以返回一个render函数, 2-可以返回一个对象
if (typeof setupResult === 'function') {
if (render) console.error('setup 函数返回渲染函数,render 选项将被忽略')
render = setupResult
} else {
setupState = setupContext
}
}
// vnode 也要和当前组件实例管理
vnode.component = instance
// 渲染函数是如何获取响应是数据的?
// 获取数据的优先级:data > props > setup
const renderContext = new Proxy(instance, {
get(t, k, r) {
const { state, props, slots } = t
// 获取slot
if (k === '$slots') return slots
// 优先从state中取数据
if (state && k in state) {
return state[k]
// 再从props中去数据
} else if (k in props) {
return props[k]
// 最后从setupState中去数据
} else if (setupState && k in setupState) {
return setupState[k]
} else {
// 报错
console.error('不存在')
}
},
set (t, k, v, r) {
// 设置的逻辑也是同样的优化及
const { state, props } = t
if (state && k in state) {
state[k] = v
} else if (k in props) {
props[k] = v
} else if (setupState && k in setupState) {
setupState[k] = v
} else {
console.error('不存在')
}
}
})
// 调用created钩子函数
created && created.call(renderContext)
// 挂载和patch中,所有响应式的数据 都会被收集为依赖, 当前数据变化时, 下面代码会被再次执行
effect(() => {
const subTree = render.call(renderContext, renderContext)
if (!instance.isMounted) {
// 挂载之前的钩子函数
beforeMount && beforeMount.call(renderContext)
// 挂载,oldvnode为null
patch(null, subTree, container, anchor)
// 组件挂载状态修改
instance.isMounted = true
// 挂载完成的钩子函数
mounted && mounted.call(renderContext)
instance.mounted && instance.mounted.forEach(hook => hook.call(renderContext))
} else {
// 执行更新前钩子函数
beforeUpdate && beforeUpdate.call(renderContext)
// 打补丁
patch(instance.subTree, subTree, container, anchor)
// 执行更新完成的钩子函数
updated && updated.call(renderContext)
}
// 记录租金贷子树
instance.subTree = subTree
}, {
// 通过调度器来执行更新
scheduler: queueJob
})
}