本文是 Vue3 源码实战专栏第 13 篇,实现组件代理对象。
前言
Vue 项目开发中,在data
中定义的数据可以通过this
获取,甚至this
上也挂载着$data
,$el
,方便用户开发时直接使用this
操作。
例如,$el
官方文档 中描述:
该组件实例管理的 DOM 根节点
根据上一篇文章《createApp之后发生了什么?》中示例,修改App.js
中App
,
window.self = null;
export const App = {
render() {
window.self = this;
return h("div", "hello " + this.msg);
},
setup() {
return {
msg: "world",
};
},
};
运行项目,页面上显示hello undefined
。
本文最终需要实现的就是能够在页面上显示出hello world
,在控制台输入self.$el
输出div
DOM。
代理数据
setup
中返回的数据,还有官网中罗列的$el
,$data
等都可以通过this
访问,底层是使用了Proxy
作为代理,当组件初始化的时,设置了代理对象。
数据的代理实现更简单,我们先来实现。
组件初始化的方法是setupStatefulComponent
,将Proxy
挂载在组件实例上。
在component.ts
中,
function setupStatefulComponent(instance) {
const Component = instance.type;
instance.proxy = new Proxy({}, {
get(target, key) {
const { setupState } = instance;
if (key in setupState) {
return Reflect.get(setupState, key);
}
},
});
const { setup } = Component;
if (setup) {
const setupResult = setup();
handleSetupResult(instance, setupResult);
}
}
上面代码,在实例instance
上挂载了代理对象,当触发get
时,可以获取到setup
的返回值setupState
对象,key
就是需要访问的属性,也就是示例中msg
。
组件实例上挂载了proxy
,在render
函数调用时,把函数的this
指向proxy
就能通过this
拿到数据了。
在renderer.ts
中,
function setupRenderEffect(instance, container) {
const { proxy } = instance;
const subTree = instance.render.call(proxy);
patch(subTree, container);
}
此时,重新打包启动项目,就可以在页面上看到了hello world
。
$el
实现$el
,可以在控制台通过self.$el
获取到div
的 DOM 结构。
想要获取到el
,需要在创建的时候缓存el
,el
的创建是在mountElement
方法中。
在renderer.ts
中
function mountElement(vnode, container) {
const { type, children } = vnode;
let el = (vnode.el = document.createElement(type));
...
}
将创建的el
,挂载到虚拟节点vnode
上。
那在访问时还是通过代理实现,即key
等于$el
时把挂载的el
返回出去,再次回到setupStatefulComponent
方法,
instance.proxy = new Proxy({}, {
get(target, key) {
const { setupState } = instance;
if (key in setupState) {
return Reflect.get(setupState, key);
}
if(key === '$el') {
return instance.vnode.el
}
},
});
但需要注意的是,这两处的el
并不是同一个,mountElement
中保存在虚拟节点上的el
是element
,也就是示例中的div
;而instance
中vnode
上的el
,其实并不存在,因为这儿是组件实例上虚拟节点。
在组件初始化中,最终是会调用setupRenderEffect
方法,该方法中会调用patch
,再执行一遍初始化,此时就是element
的逻辑,那虚拟节点subTree
上的el
就是在创建element
时绑定vnode
上的el
。这时也就是在patch
之后,将这个el
绑定到组件vnode
上,在代理触发$el
返回就能拿到组件实例上vnode
的el
。
在renderer.ts
中,
function mountComponent(initialVnode, container) {
const instance = createComponentInstance(initialVnode);
setupComponent(instance);
setupRenderEffect(instance, initialVnode, container);
}
function setupRenderEffect(instance, initialVnode, container) {
const { proxy } = instance;
const subTree = instance.render.call(proxy);
patch(subTree, container);
initialVnode.el = subTree.el;
}
再次执行打包启动项目,在浏览器控制台中可以打印出self.$el
。
重构
setupStatefulComponent
中实现代理的逻辑,抽离出来。
新建文件componentPublicInstance.ts
,
const publicPropertiesMap = {
$el: (i) => i.vnode.el,
};
export const PublicInstanceProxyHandlers = {
get({ _: instance }, key) {
const { setupState } = instance;
if (key in setupState) {
return Reflect.get(setupState, key);
}
const proxyGetter = publicPropertiesMap[key];
if (proxyGetter) {
return proxyGetter(instance);
}
},
};
上面代码,通过target
对象传入instance
参数;定义一个publicPropertiesMap
对象,方便后续$data
,$props
等的扩展。
component.ts
中setupStatefulComponent
方法相应修改,
function setupStatefulComponent(instance) {
const Component = instance.type;
instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers);
const { setup } = Component;
if (setup) {
const setupResult = setup();
handleSetupResult(instance, setupResult);
}
}
总结
无论是setup
返回的数据还是data
数据,还有$data
,$el
…都是通过Proxy
代理,在组件初始化阶段执行。代理数据的实现是将setup
执行结果setupState
中数据返回,再在渲染render
时绑定this
指向;像$el
这类便于用户开发使用的,同样是在Proxy
中处理,根据不同的key
处理不同的逻辑。
原文始发于微信公众号(前端一起学):Vue中this是怎么直接拿到数据的?
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/191236.html