14-实现组件代理

138 阅读2分钟
  • vue的render函数是可以通过this.xxx去获取options api和 composition api的数据的
  • 基于hello word的例子,实现数据代理

实现setupState注册

思考

  1. handleSetupResult函数中把setup返回的数据注入到了实例的setupState对象中
  2. 所以可以在setupComponent执行完后,去挂载render的this,让其可以取到setupState对象中的数据
  3. vue中render挂载的this实例中可能有 datadata、props之类的数据,所以统一用proxy来收集
  4. setupRenderEffect函数中会去执行render,并绑定render的this
function mountComponent(vnode, container) {
  // 初始化Component实例
  const instance = createComponentInstance(vnode);
  // 初始化setup函数return的数据
  setupComponent(instance, container);
+  /** 挂载render的this
+   * 1. 我们可以借助proxy来挂载我们的实例属性,让proxy代理
+   * 2. 最后render的时候,把this指向这个proxy,这样就可以通过 this.xx -> proxy.get(xx) 获取数 +     据
+   */
+  createProxyInstance(instance);
  // setupRenderEffect
  setupRenderEffect(instance, container);
}

// 初始化组件代理
function createProxyInstance(instance) {
  instance.proxy = new Proxy(
    {},
    {
      get(target, key) {
        let { setupState } = instance;
        // 获取setup返回的数据
        if (key in setupState) {
          return setupState[key];
        }
      },
    }
  );
}

function setupRenderEffect(instance, container) {
+ const { proxy } = instance;
+  // 通过render函数,获取render返回虚拟节点,并绑定render的this
+  const subTree = instance.render.call(proxy);
  // 最后通过patch的processElement,将subTree渲染到container(节点)上
  patch(subTree, container);
}

实现 $el

思考

  1. this.$el 取的是当前组件dom
  2. 在 processElement函数中 创建的el其实就是当前的$el
  3. 可以在setupRenderEffect函数中将processElement函数创建的el挂载到vnode上

component.ts

  1. 注册$el到实例
function setupRenderEffect(instance, container) {
  ----other code ----
  
  /** 挂载当前的dom元素到$el
   * 1. 当遍历完所有Component组件后,会调用processElement
   * 2. 在processElement中,会创建dom元素,把创建的dom元素挂载到传入的vnode里面
   * 3. 当前的dom元素也就是processElement中创建的dom元素
   */
+  vnode.el = subTree.$el;
}
  1. proxy收集
function createProxyInstance(instance) {
  // instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers);
  instance.proxy = new Proxy(
    {},
    {
      get(target, key) {
+        let { setupState, vnode } = instance;
+        if (key === "$el") {
+          return vnode.el;
+        }
        // 获取 setup返回的数据
        if (key in setupState) {
          return setupState[key];
        }
      },
    }
  );
}

element.ts

export function processElement(vnode, container) {
  const { type, props, children } = vnode;
  // 创建根元素
  const el = document.createElement(type);
  // 将dom元素挂载到实例
  vnode.$el = el;
  
  ----other code----
}

优化代码

优化 createProxyInstance

function createProxyInstance(instance) {
  instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers);
}

抽离proxy,创建 componentPublicInstanceProxyHandlers.ts

/*
 * @Author: Lin zefan
 * @Date: 2022-03-23 17:52:57
 * @LastEditTime: 2022-03-23 22:26:50
 * @LastEditors: Lin zefan
 * @Description:
 * @FilePath: \mini-vue3\src\runtime-core\componentPublicInstanceProxyHandlers.ts
 *
 */

// 扩展的实例Map
const PublicInstanceMap = {
  $el: (i) => i.vnode.el,
};

export const PublicInstanceProxyHandlers = {
  get({ _: instance }, key) {
    let { setupState } = instance;
    // 获取 setup返回的数据
    if (key in setupState) {
      return setupState[key];
    }
    // 获取instance实例对象
    const publicGetter = PublicInstanceMap[key];
    if (publicGetter) {
      return publicGetter(instance);
    }
  },
};