组件的代理对象

组件的代理对象

我们在组件中使用 this 可以访问到 setupState、props、attrs、slots、$refs 等属性,这些属性都是通过代理对象实现的。 组件的代理对象实现:

javascript
/**
 * 初始化组件
 */
export function setupComponent(instance) {
  /**
   * 初始化属性
   */

  initProps(instance)

  setupStatefulComponent(instance)
}

const publicPropertiesMap = {
  $attrs: (instance) => instance.attrs,
  $slots: (instance) => instance.slots,
  $refs: (instance) => instance.refs,
  $nextTick: (instance) => {
    // TODO nextTick
  }
}

const publicInstanceProxyHandlers = {
  get(target, key) {
    const { _: instance } = target

    const { setupState, props } = instance

    /**
     * 如果访问了某个属性,我先去 setupState 里面找
     * 如果没有,我再去 props 里面找
     */

    // 去 setupState 里面找
    if (hasOwn(setupState, key)) {
      return setupState[key]
    }

    // 去 props 里面找
    if (hasOwn(props, key)) {
      return props[key]
    }

    /**
     * $attrs
     * $slots
     * $refs
     */
    if (hasOwn(publicPropertiesMap, key)) {
      const publicGetter = publicPropertiesMap[key]
      return publicGetter(instance)
    }

    /**
     * 如果实在找不到,只能掀被窝了
     */
    return instance[key]
  },
  set(target, key, value) {
    const { _: instance } = target
    const { setupState } = instance

    if (hasOwn(setupState, key)) {
      /**
       * 修改 setupState
       */
      setupState[key] = value
    }

    return true
  }
}

function setupStatefulComponent(instance) {
  const { type } = instance

  /**
   * 创建代理对象,内部访问 setupState props $attrs $slots 这些
   */
  instance.proxy = new Proxy(instance.ctx, publicInstanceProxyHandlers)

  if (isFunction(type.setup)) {
    const setupContext = createSetupContext(instance)
    // 保存 setupContext
    instance.setupContext = setupContext
    const setupResult = type.setup(instance.props, setupContext)

    handleSetupResult(instance, setupResult)
  }

  if (!instance.render) {
    // 如果上面处理完了,instance 还是 没有 render,那就取组件的配置里面拿
    instance.render = type.render
  }
}

function handleSetupResult(instance, setupResult) {
  if (isFunction(setupResult)) {
    // 如果 setup 返回了函数,就认定为是 render
    instance.render = setupResult
  } else if (isObject(setupResult)) {
    // 如果返回了对象,就是状态
    instance.setupState = proxyRefs(setupResult)
  }
}

/**
 * 创建 setupContext
 */
function createSetupContext(instance) {
  return {
    get attrs() {
      return instance.attrs
    }
  }
}

顺便更新一下 computedUpdateFn

javascript
const componentUpdateFn = () => {
  /**
   * 区分挂载和更新
   */
  if (!instance.isMounted) {
    const { vnode, render } = instance
    // 调用 render 拿到 subTree,this 指向 组件的代理对象
    const subTree = render.call(instance.proxy)
    // 将 subTree 挂载到页面
    patch(null, subTree, container, anchor)
    // 组件的 vnode 的 el,会指向 subTree 的 el,它们是相同的
    vnode.el = subTree.el
    // 保存子树
    instance.subTree = subTree
    // 挂载完了
    instance.isMounted = true
  } else {
    let { vnode, render, next } = instance

    if (next) {
      // 父组件传递的属性触发的更新,会走这里
      updateComponentPreRender(instance, next)
    } else {
      // 自身属性触发的更新,会走这边
      next = vnode
    }

    const prevSubTree = instance.subTree
    // 调用 render 拿到 subTree,this 指向 组件的代理对象
    const subTree = render.call(instance.proxy)
    // 将 subTree 挂载到页面
    patch(prevSubTree, subTree, container, anchor)
    // 组件的 vnode 的 el,会指向 subTree 的 el,它们是相同的
    next.el = subTree.el
    // 保存这一次的 subTree
    instance.subTree = subTree
  }
}
属性传递(props和attrs)
组件的异步更新
欢迎来到前端练习生ZM的小站