手撸mini-vue之组件代理对象

578 阅读1分钟

实现目标:render函数中通过this获取setup返回的属性,获取$el

export const App = {
  render() {
    return h(
      "div",
      {
        id: "root",
        class: ["red", "blue"],
      },
      "hi, " + this.msg
    )
  },
  
  setup() {
    return {
      msg: 'mini-vue'
    }
  }
}

setupState 获取

在之前实现Component初始化主流程的过程中,我们将setup的返回值赋值给了组件实例instance的属性setupState。因此我们只需要

  1. 在初始化有状态的组件时,通过proxyget操作进行代理。
  2. 在调用组件实例对象的render函数时将this指向proxy
// component.ts
function setupStatefulComponent(instance) {
  const Component = instance.type

  // 利用 Proxy 对组件实例对象的 proxy property 的 get 进行代理
  instance.proxy = new Proxy(
    {},
    {
      get(target, key) {
        const { setupState } = instance

        if (key in setupState) {
          return setupState[key]
        }
      }
    }
  )
  
  ...
}
// renderer.ts
function setupRenderEffect(instance, container) {
  const { proxy } = instance

  // 获取 VNode 树,将 this 指向 proxy
  const subTree = instance.render.call(proxy)
  patch(subTree, container);
}

$el 获取

首先在创建VNode的时候增加一个el的属性,用于保存组件的根元素

// vnode.ts
export function createVNode(type, props?, children?) {
  const vnode = {
    type,
    props,
    children,
    el: null
  };
  
  return vnode;
}

Element初始化的时候赋值将根据vnode.type创建的DOM元素赋值给vnode.el

// renderder.ts
function mountElement(vnode, container) {
  const el = (vnode.el = document.createElement(vnode.type));
  
  ...
}

在获取VNode树并且递归patch后,将VNode树的el赋值给VNode

// renderder.ts
function setupRenderEffect(instance, vnode, container) {
  const { proxy } = instance;
  const subTree = instance.render.call(proxy);
  patch(subTree, container);
  vnode.el = subTree.el;
}

在处理instance.proxy的地方,判断$el

// component.ts
function setupStatefulComponent(instance) {
  const Component = instance.type

  // 利用 Proxy 对组件实例对象的 proxy propertyget 进行代理
  instance.proxy = new Proxy(
    {},
    {
      get(target, key) {
        const { setupState } = instance

        if (key in setupState) {
          return setupState[key]
        }
        
        if (key === '$el') {
          return instance.vnode.el
        }
      }
    }
  )
  
  ...
}

代码重构

// componentPublicInstance.ts

// 保存 instance上的 property 对应的 getter
const publicPropertiesMap = {
  $el: i => i.vnode.el
}

export const PublicInstanceHandlers = {
  get({_:instance}, key) {
    const { setupState } = instance;
    
    if (key in setupState) {
      return setupState[key];
    }
    
    const publicGetter = publicPropertiesMap[key];
    if (publicGtter) {
      return publicGetter()
    }
  }
}
// component.ts
function setupStatefulComponent(instance) {
  const component = instance.type;
  instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyhandlers);
  
  ...
}