Vue3 源码:render 与组件挂载 setupComponent 流程
拿到虚拟节点之后,从「一个 JS 对象」到「页面上真实的 DOM」中间要走一串调用。这篇把这条链路拆开走一遍,看清一个组件从挂载到能渲染,Vue 到底做了哪几件事。
整条路径是:render → patch(按 vnode 类型分流)→ processComponent → mountComponent → 创建组件实例 → setupComponent(处理 props、执行 setup、挂上 render 函数)→ setupRenderEffect(把渲染包成副作用)。分这么多层不是为了好看:patch 是递归入口,元素和组件都要经过它,所以必须独立出来;组件实例这一层承载 props、slots、setup 返回值和生命周期状态,是后续所有更新的依据。
理解这段的关键在于「组件比元素多一层」:元素可以直接建 DOM,而组件得先跑一遍 setup 拿到它的 render 函数,再由这个 render 函数产出新的 vnode,然后重新进入 patch。组件树就是靠这个递归展开的。
render
render 只是入口,实际工作全在 patch 里。patch 靠 vnode.type 的类型做分流:字符串走 processElement 直接建 DOM;对象说明是组件选项,走 processComponent。
mountComponent 里三步走:createComponentInstance 建一个组件实例对象(挂 props、slots、setupState 等状态),setupComponent 初始化这个实例,setupRenderEffect 把渲染逻辑包成响应式副作用——这一步是响应性和运行时接上的地方,之后数据变化就能自动触发重渲染。
setupStatefulComponent 里那个 instance.proxy 是模板里能直接写 {{ name }} 而不用写 this.setupState.name 的原因:它代理了 instance.ctx,PublicInstanceProxyHandlers 在 get 时依次去 setupState、props、data 里找这个 key。注释也说明了为什么必须用 instance.proxy 而不是直接用 instance.ctx——ctx 在开发和生产环境下结构不同。
setup() 的返回值分两种情况:返回对象就存进 setupState 供模板访问(这里 handleSetupResult 只处理了对象分支,返回函数的情况是当作 render 函数用,属于未实现的部分),最后 finishComponentSetup 把组件选项里的 render 挂到实例上,渲染副作用之后就调它。
function render(vnode, container) {
patch(vnode, container);
}
function patch(vnode, container) {
const { children, type } = vnode;
if (typeof vnode.type === 'string') {
console.log('处理 element');
processElement(vnode, container);
}
// 虚拟节点的类型为对象时,代表处理的是一个组件
else if (isObject(vnode.type)) {
console.log('处理 component');
// 处理组件
processComponent(vnode, container);
}
}
// 处理组件
function processComponent(vnode, container) {
mountComponent(vnode, container);
}
function mountComponent(vnode, container) {
// 1. 先创建一个 component instance
const instance = createComponentInstance(vnode);
setupComponent(instance);
setupRenderEffect(instance, vnode, container);
}
export function setupComponent(instance) {
// 1. 处理 props
// 取出存在 vnode 里面的 props
const { props, children } = instance.vnode;
setupStatefulComponent(instance);
}
function setupStatefulComponent(instance) {
console.log('创建 proxy');
// proxy 对象其实是代理了 instance.ctx 对象
// 我们在使用的时候需要使用 instance.proxy 对象
// 因为 instance.ctx 在 prod 和 dev 坏境下是不同的
instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers);
const Component = instance.vnode.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;
instance.render = Component.render;
}

还没有留言,来发表第一条吧~