在这一小节呢,我们将会去实现组件的代理对象
在第 16 小节中我们写了一个 helloworld 的例子,在 render 方法中我们用到了 setup 返回的 state
那么在这个部分,我们就来实现以下 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 了。
其实在文档中可以通过 this.$x 来获取某些数据
- $el
- $data
- $props
- ......
再加上上面还可以获取到 setupState,为了能够解耦,我们就可以来统一管理 this,而不是零零散散的再挂载到 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)
}
通过对于官网中 $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 了。