在我们开发时使用this.$xx的时候实际上h函数this指向不是天然性指向节点初始化后的this,所以我们这章就实现this.$xx,顺便封装下映射关系,可以让后续的this.$xx实现起来更简单没可读性也更高
h函数this指向及$xx问题
我们在h中使用this时,拿到的是setup return出去的值
这里我是直接抽离出去了,省的看到后面还要重新捋一遍
component.ts
export function createComponentInstance(vnode) {
const component = {
vnode,
type: vnode.type,
setupState: {} // 在这里声明好setupState,以便后续使用
}
return component
}
function setupStatefulComponent(instance: any) {
const Component = instance.type
// 就在这里初始化一下,用代理的方式完成
instance.proxy = new Proxy({_: instance}, publicInstanceProxyHandlers)
const { setup } = Component
if (setup) {
// setup可以返回一个function,也可以返回一个object
// 返回function我们认为是它是组件的一个render函数
// 返回object认为是一个会把object注入到当前的组件的上下文中
const setupResult = setup()
handleSetupResult(instance, setupResult)
}
}
componentPublicInstance.ts
这个是抽离出去的文件
这里用的就是映射关系实现的,后续有别的$xx需要实现只需要在map里加上相应的代码即可
const publicPropertiesMap = {
$el: (i) => i.vnode.el
}
export const publicInstanceProxyHandlers = {
get({ _: instance }, key) {
// setupState
// 实现this指向问题
const { setupState } = instance
if (key in setupState) {
return Reflect.get(setupState, key)
}
// 实现$xx
const publicGetter = Reflect.get(publicPropertiesMap, key)
if (publicGetter) {
return publicGetter(instance)
}
}
}
vnode.ts
在vnode里提前声明变量
export function createVNode(type, props?, children?) {
const vnode = {
type,
props,
children,
el: null // 实现$el用的
}
return vnode
因为$el是真实dom,所以需要在初始化完成之后的时候赋值一下,以保证拿到的是对的
所以我们需要在patch结束后进行赋值
render.ts
function mountElement(vnode: any, container: any) {
const { type, props, children } = vnode
const el = (vnode.el = document.createElement(type))
// string array<TODO>
if (typeof children === 'string') {
el.textContent = children
}
else if (Array.isArray(children)) {
mountChildren(vnode, el)
}
for (const key in props) {
const value = props[key]
el.setAttribute(key, value)
}
container.append(el)
}
function setupRenderEffect(instance: any, initialVNode: any, container: any) {
const { proxy } = instance
// subTree是一个VNode tree
const subTree = instance.render.call(proxy)
// vnode -> patch
// vnode -> element -> mountElement
patch(subTree, container)
// element -> mount
initialVNode.el = subTree.el
}
使用
这里直接在控制台调试输出看看有没有值就行
window.self = null // 全局绑定
export const App = {
// 没搞编译,所以template不写,写render就行
// 这里假设用户写了render
render() {
window.self = this // 赋值this
// 返回虚拟VNode
return h('div',
{
id: 'root',
class: ['bgc-red', 'font-blue']
},
`hi,${this.msg}`
// this.$el -> get root element
// string
// 'h1,xin-vue'
// array
// [h('p', { class: 'font-green' }, 'hi'), h('p', {class: 'font-pink'}, 'xin-vue')]
) // 没实现this问题
},
setup() {
// composition api
return {
msg: 'xin-vue'
}
}
}
结语
实现起来很简单,分辨清楚赋值的是元素还是组件以及赋值时机的问题就很容易实现,后续如果需要添加别的$xx也只需要在map里添加相关代码就行,避免造成代码冗余和美观度