Skip to content

Latest commit

 

History

History
180 lines (142 loc) · 4.95 KB

18. 组件的代理对象.md

File metadata and controls

180 lines (142 loc) · 4.95 KB

组件的代理对象

在这一小节呢,我们将会去实现组件的代理对象

在第 16 小节中我们写了一个 helloworld 的例子,在 render 方法中我们用到了 setup 返回的 state

1. 实现 setupState

那么在这个部分,我们就来实现以下 setupState,我们知道,要想在 render 函数获取到 this,就要把 setupState 给绑定进来。

// render.ts

// other code ...

// 在这里呢我们将组件实例进行 render,所以在这里就可以将 render 方法的 this 给绑定未 setupState
function setupRenderEffect(instance, container) {
  const subTree = instance.render()
  patch(subTree, container)
}

首先,我们可以在这个 instance 中创建一个 setupState

// component.ts
export function createComponentInstance(vnode) {
  // 这里返回一个 component 结构的数据
  const component = {
    vnode,
    type: vnode.type,
    setupState: {},
  }
  return component
}

而我们在处理 setup 函数时就已经挂载到 instance 上了

// component.ts
function handleSetupResult(instance, setupResult) {
  if (typeof setupResult === 'object') {
    // 这里就进行了挂载
    instance.setupState = setupResult
  }
  finishComponentSetup(instance)
}

所以我们 render 中

function setupRenderEffect(instance, container) {
  // 解构出 setupState
  const { setupState } = instance
  // 将 render.this 变成 setupState
  const subTree = instance.render.call(setupState)
  patch(subTree, container)
}

此时我们再去测试模板,发现在 render 中就已经可以获取到 this 了。

2. 实现 $el

其实在文档中可以通过 this.$x 来获取某些数据

查看文档

  • $el
  • $data
  • $props
  • ......

再加上上面还可以获取到 setupState,为了能够解耦,我们就可以来统一管理 this,而不是零零散散的再挂载到 this 上。

2.1 拦截对于 instance 中 this 的操作

function setupStatefulComponent(instance) {
  const component = instance.vnode.type

  // 在这里对于 instance 的 this 进行拦截
  instance.proxy = new Proxy({}, componentPublicInstanceProxyHandlers)
  
  // other code ...
}
export const componentPublicInstanceProxyHandlers = {
  get(target, key) {
      return target.setupState[key]
  },
}

这样我们再回到 render 中,OK,添加上之后我们记得要去测试一下,这样的逻辑基本就可以跑通了

  • 首先我们在初始化的时候调用 setup 方法,获取到返回值
  • 将这个返回值挂载到 instance.setupState 上
  • 这里 instance.render 里的 this 就是 instance.proxy
  • 也就是我们在模板中的 render 方法里的 this 就是 instance.proxy
  • 例如我们访问 this.title,instance.proxy -> target.setupState['title']
function setupRenderEffect(instance, container) {
  // 这里就进入到了上面的 proxy 中
  const subTree = instance.render.call(instance.proxy)
  patch(subTree, container)
}

2.2 实现 $el

通过对于官网中 $el 的描述我们发现,通过对 this.$el 可以获取到组件中的根 DOM 节点。我们知道创建 DOM 是在 mountElement 函数中实现的,此时我们就可以将这个 el 存在 vnode 里面

// render.ts
function mountElement(vnode, container) {
  // 创建 dom,并将 dom 存在 vnode.el 中
  const domEl = (vnode.el = document.createElement(domElType))
  
  // other code ...
}
// vnode.ts
export function createVNode(type, props?, children?) {
  // 这里先直接返回一个 VNode 结构
  return {
    type,
    props,
    children,
    // 再创建 vnode 时创建一个空的 el
    el: null,
  }
}

接下来,我们就可以在那个 ProxyHandlers 里面写对于 $el 的数据了。但是现在的问题在于,$el 在 vnode 里面的,我们要把 vnode 给传到这个 setupStatefulComponent 中。

// Proxy 传入 target,_ 作为 instance.vnode
instance.proxy = new Proxy(
    { _: instance.vnode },
    componentPublicInstanceProxyHandlers
)
export const componentPublicInstanceProxyHandlers = {
  get(target, key) {
    // 如果我们访问的是 this.$el,那么就会返回 vnode.el
    if (key === '$el') {
      const { _: vnode } = target
      return vnode.el
    }
    return target.setupState[key]
  },
}

但是现在呢我们还是获取不到 $el 的,这是因为我们走了两边的 patch,第一遍是我们需要的 vnode,但是我们是在第二遍获取到的 el,也就是在这里

function setupRenderEffect(instance, vnode, container) {
  const { setupState } = instance
  const subTree = instance.render.call(setupState)
  // 将转换好的 component 进行 element 分支的 patch
  patch(subTree, container)
  // 通过 patch,走到 mountElement 方法中,将传入的 subTree.el 变成为根节点
  // 在这里再将 vnode.el = subTree.el 即可
  vnode.el = subTree.el
}

此时我们再去测试发现就可以访问到 this.$el 了。