◆

Vue3 源码:render 与组件挂载 setupComponent 流程

2023-02-20 01:350

拿到虚拟节点之后,从「一个 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;
}
💬留言板0

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

PAGE LOAD / LIVEVue3 源码:render 与组件挂载 setupComponent 流程加载中