实现 this
指向
ts
import { h } from '../lib/guide-mini-vue.esm.js';
export const App = {
name: 'App',
render() {
window.self = this;
return h('div', { id: 'app' }, [
h('p', { class: 'red' }, 'Hello' + this.msg),
]);
},
setup() {
return {
msg: 'jerry'
}
}
}
在这个例子中,我们需在 render
中实现 this
的指向,在 setupRenderEffect
调用 instance.render
时,理论上是让 render
函数指向 setupState
。
setupState
指的是 setup
函数的返回结果
ts
// 伪代码
instance.render.call(setupState);
实现
在 createComponentInstance
这里,给 instance
初始化了 setupState
属性
ts
function createComponentInstance(vnode) {
const component = {
setupState: {},
}
}
在 setupStatefulComponent
这里,实现组件对象的代理
ts
// runtime-core/component.ts
function setupStatefulComponent(instance) {
const Component = instance.type;
const { setup } = Component;
// 实现组件对象的代理
instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers)
if(setup) {
const setupResult = setup();
// 处理 setup 函数的返回结果
handleSetupResult(instance, setupResult);
}
}
ts
// runtime-core/componentPublicInstance.ts
export const PublicInstanceProxyHandlers = {
get({ _: instance }, key) {
const { setupState, props } = instance;
const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key);
if(hasOwn(setupState, key)) {
return setupState[key];
}
}
}
回顾 runtime-core 的流程中,等到 setupRenderEffect
执行时,已完成实例对象 proxy
的绑定。
ts
function mountComponent() {
createComponentInstance();
setupComponent();
setupRenderEffect();
}
在 setupRenderEffect
内调用 instance.render
时,把实例的代理对象 proxy
绑定到 render
,那么 render
函数内的 this
便指向了 instance.proxy
ts
// rumtime-core/renderer.ts
function setupRenderEffect(instance) {
const { proxy } = instance;
const subTree = instance.render.call(proxy);
patch(subTree, container);
}
这里需要注意的是,被 Proxy
代理的目标对象是 instance
,但是我们在 render
函数内部访问 this.msg
的时候,不是访问 instance
的属性,而是 instance.proxy
的属性。
在访问 this.msg
的时候,被 Proxy
拦截处理,内部的机制是,从 instance.setupState
里面获取 msg
属性值并返回。
ts
const { setupState, props } = instance;
if(hasOwn(setupState, key)) {
return setupState[key];
}
$el
vnode
的 el
ts
function createVNode(type, props?, children) {
const vnode = {
el: null,
}
}
开始给 el
赋值是在 mountElement
函数内部处理。
ts
function mountElement(vnode, container) {
const el = vnode.el = document.createElement(vnode.type); // 开始给 el 赋值
}
ts
function mountComponent(initialVNode, container) {
const instance = createComponentInstance(initialVNode)
setupComponent(instance);
// 把 initialVNode 传给 setupRenderEffect
setupRenderEffect(instance, initialVNode, container);
}
ts
function setupRenderEffect(instance, initialVNode, container) {
const subTree = instance.render.call(proxy);
patch(subTree, container);
// 经过 patch 之后,subTree 获得了 el
initialVNode.el = subTree.el;
}
为什么是 initialVNode.el = subTree.el;
呢?
vnode 和 subTree 的区别
比如,在这个例子中:
js
const Child = {
name: 'Child',
render() {
return h('div', {}, 'child')
}
};
const App = {
name: 'App',
render() {
return h('div', { class: 'app' }, [
h(Child, {})
])
}
}
App
的 el
应该是
html
<div class="app">
<div class="child">child</div>
</div>
Child
的 el
应该是:
html
<div class="child">child</div>
App
vnode
的 el
一开始 App
传给 createVNode
得到了一个 App
的 initialVnode
,
ts
{
type: App,
props: {},
children,
el: null,
}
initialVnode
传给了 render
接着 patch(initialVnode)
接着 mountComponent(initialVnode)
接着来到 setupRenderEffect(initialVnode)
接着执行 App instance
的 render
,得到 subTree
ts
// App vnode 的 subTree
subTree = h('div', { class: 'app' }, [
h(Child, {})
])
h
函数返回的是:
ts
{
type: 'div',
props: {
class: 'app'
},
children: h(Child, {}),
el: null,
}
也就是 subTree
指的是:
ts
{
type: 'div',
props: {
class: 'app'
},
children: h(Child, {}),
el: null,
}
接着 patch(subTree)
接着 mountElement(subTree)
接着 subTree.el = document.createElement('div')
至此,patch 完成
接着 initialVnode.el = subTree.el
最后,initialVnode
即 App
对应的这个 vnode
的 el
为 document.createElement('div')
Child
vnode
的 el
在第二次 patch
后,根据 type='div'
来到 mountElement
接着判断到 vnode.children
是数组
接着 mountChildren
接着再次 patch
ts
mountChildren(vnode) {
vnode.children.forEach(v => {
patch(v);
})
}
这里,v
表示 Child
的 vnode
根据 type=Child
接着来到 mountComponent
,这时的 initialVnode
为 Child
的 vnode
ts
initialVnode = {
type: Child,
props: {},
children: {},
el: null,
}
接着来到 setupRenderEffect
接着执行 Child
的 render
得到 Child
的 subTree
ts
const subTree = instance.render.call(proxy);
即
ts
subTree = h('div', { class: 'child' }, 'child')
然后再 patch
最终 Child vnode
的 el
是 <div class="child">child</div>
el
挂载到 instance.proxy
代理对象
ts
const PublicPropertiesMaps = {
$el: (i) => i.vnode.el,
}
export const PublicInstanceProxyHandlers = {
get({ _: instance }, key) {
const { setupState, props } = instance;
// 省略...
// 在这里统一处理 $el、$props 等对象
const publicGetter = PublicPropertiesMaps[key];
if(publicGetter) {
return publicGetter(instance);
}
}
}
这样,在 render
中访问 this.$el
,就可获取到组件对应的 el
。