vue3实现组件代理对象
首先我们在render函数中使用this.msg访问setup中return出来的对象的值的时,会发现访问到的值都是undefined。 所以我们需要在render中通过this访问到setup函数中返回出来的对象的值!
代理对象初步构建
1 要实现这个,第一点我们就应该找到render函数调用的位置,并且改变render函数的this指向(如下图所示,render函数的调用位置就在setupRenderEffect函数中)
2 接着我们就要思考改变的this指向的对象应该是谁,这时候我们就想着需要一个代理对象来替我们做这件事情。所以就有了在component实例中去挂载上一个代理对象,然后对代理对象proxy构造函数中的get做一个代理访问的操作(当我访问对象中的值的时候,我们就会到setupState中去看看是不是setupState对象中的属性,如果是就放回setupState中属性的值出来)
注:setupState其实就是我们前面对setup函数放回出来对象结果的一个存储;
如下图:
3 最后我们就在步骤一中的render函数调用处使用call去改变this指向,指向代理对象
$el的实现
有了上面的代理对象的初步构建,$el的实现也就不太难了
1 首先我们也是在代理对象中判断,如果key==="$el"时就返回实例虚拟节点上的el就行了,然后我们发现其实我们实例虚拟节点上的el并没有挂载或者处理,所以我们下面就需要对这个对象的el进行一个处理
2 然后我们需要找到创建节点的位置也就是mountElement函数中,并且将创建好的节点挂载到虚拟节点的el上;当redner函数调用完毕放回的subTree中就可以拿到前面mountElement函数中保存的vnode中的el,当patch执行完毕后,所有的element都mount完毕,我们就把subTree.el赋值给vnode.el就可以了。
3 为了验证我们的el确实挂载成功了,我们需要在app.js中去给指定self变量,然后在render中给self全局属性赋this对象,在浏览器控制台中通过self去查看el是否挂载成功,实现如下:
结果如图:
使用vue3中我们可以知道,其实我们还有其他全局变量如 data $props...,如图:
因此我们就考虑把代理的proxy中的handlers抽离出去
4 抽离出来的PublicInstanceProxyHandlers中的我们可以通过map去代替掉用if来判断key的代码,让代码更简洁高效
5 最后我们还可以优化一个,就是render中的mountComponent的那几个初始化节点的命名,可以将vnode改成initialVNode,更加具有语义化
修改的文件及代码:
App.js
import {h} from '../../lib/guide-ljp-vue.esm.js'
window.self = null
export const App = {
// .vue
// <template></template> 最总转化成render函数
// render 由于还没有实现编译功能 所以先直接写render函数
render() {
window.self = this
// ui
return h('div',
{
id:'root',
class:['red','hard']
},
// 在render中通过this访问到setup函数中返回出来的对象的值
'hi, '+this.msg
// [h('p',{class:'red'},'hi'),h('p',{class:'blue'},'mini-vue')]
)
},
setup(){
return{
msg:'mini-vueljp'
}
}
}
components.ts
import { PublicInstanceProxyHandlers } from "./componentPublicInstance"
export function createComponentInstance(vnode) {
const component = {
vnode,
type:vnode.type,
setupState:{}
}
return component
}
export function setupComponent(instance) {
// initProps
// initSlots
setupStatefulComponent(instance)
}
function setupStatefulComponent(instance){
const Component = instance.type
instance.proxy = new Proxy(
{_:instance},
PublicInstanceProxyHandlers
)
const {setup} = Component
if(setup){
const setupResult = setup()
handleSetupResult(instance,setupResult)
}
}
function handleSetupResult(instance,setupResult){
// function Object
if(typeof setupResult === 'object'){
instance.setupState = setupResult
}
finishComponentSetup(instance)
}
function finishComponentSetup(instance){
const Component = instance.type
if(Component.render){
instance.render = Component.render
}
}
render.ts
import { isObject } from "../shared/index"
import { createComponentInstance, setupComponent } from "./components"
export function render(vnode,container){
// patch
patch(vnode,container)
}
function patch(vnode,container){
// debugger
if(isObject(vnode.type)){
// 处理组件
processComponent(vnode,container)
} else {
processElement(vnode,container)
}
}
function processComponent(vnode,container){
mountComponent(vnode,container)
}
function processElement(vnode,container) {
mountElement(vnode,container)
}
function mountElement(vnode,container){
// 保存根节点到vnode上
const el = ( vnode.el = document.createElement(vnode.type))
const {children,props} = vnode
// children
if(typeof children === 'string') {
el.textContent = children
} else if(Array.isArray(children)) {
mountChildren(children,el)
}
// props
for(const key in props){
const val = props[key]
el.setAttribute(key,val)
}
container.append(el)
}
function mountChildren(children,el) {
children.forEach(v => {
patch(v,el)
})
}
function mountComponent(initialVNode,container) {
const instance = createComponentInstance(initialVNode)
setupComponent(instance)
setupRenderEffect(instance,initialVNode,container)
}
function setupRenderEffect(instance,initialVNode,container) {
// render函数调用的地方 call改变this指向代理的对象
const {proxy} = instance
const subTree = instance.render.call(proxy)
patch(subTree,container)
// element 所有都mount完毕
initialVNode.el = subTree.el
}
vnode.ts
export function createVNode(type,props?,children?){
const vnode = {
type,
props,
children,
el: null
}
return vnode
}
componentPublicInstance.ts
const publicPropertiesMap = {
$el:(i)=>i.vnode.el
}
export const PublicInstanceProxyHandlers = {
get({_:instance},key){
// setupState
const {setupState} = instance
if(key in setupState){
return setupState[key]
}
// if(key === '$el'){
// return instance.vnode.el
// }
const publicGetter = publicPropertiesMap[key]
if(publicGetter){
return publicGetter(instance)
}
// 使用vue3中我们可以知道,其实我们还有其他全局变量如 $options $data $props...
}
}