实现组件的 props
1. 实现挂载一个组件
我们在进行 render 的时候,还可以挂载一个组件
// Foo.js
import { h } from '../../lib/mini-vue.esm.js'
export const Foo = {
setup(props) {},
render() {
return h('div', {}, 'Hello World')
},
}
// App.js
render() {
return h(
'div',
{
class: 'red', // event
onClick() {
console.log('click')
},
onMousedown() {
console.log('mousedown')
},
},
[
// 挂载一个组件
h(Foo, { class: 'blue' }),
]
)
},
通过测试我们发现,我们之前写的代码其实是支持挂载一个组件的,我们再来复习一个 render 的调用流程:
createAppmount创建 App.js 的 VNode,并调用 render 方法来挂载这个 VNode
进入
render方法,调用patch进入
patch方法,首先需要判断一下此时 App.js 组件的类型,是一个 statefulComponent进入
processComponent方法,进入mountComponent方法,创建 App.js 的组件实例进入
setupComponent,传入组件实例在这里进行初始化
setupStatefulComponent,处理 setupfinishSetup,将 component 的 render 挂载到 instance 的 render调用
instance.render返回 VNode 树再次进入
patch逻辑,此时我们的 VNode 已经是 element 了进行 patchElement,此时对 children 再次递归 patch
如果某个 child 是 component,那么就可以进行挂载了
所以这样就可以来挂载一个组件了。
2. 实现挂载 props
首先,一个组件的 setup 可以传入一个参数,该参数就是 props
import { h } from '../../lib/mini-vue.esm.js'
export const Foo = {
// setup 第一个参数是 props
setup(props) {
console.log(props)
},
render() {
return h('div', {}, 'Hello World')
},
}
那么这里的 props 是在哪里传入的呢?是在父组件的 render 的时候传递的
return h(
'div',
{},
[
// 第二个参数就是 props
h(Foo, { count: 1 }),
]
)
那么我们该如何实现呢?首先,我们在处理 h 的时候就是 createVNode,第二个参数会被挂载到 vnode 实例的 props 属性。那我们又是在哪里进行处理 statefulComponent 的呢?
// components.ts
export function setupComponent(instance) {
// 在这里进行处理 props,此时传入 instance,instance.vnode.props
initProps(instance, instance.vnode.props)
setupStatefulComponent(instance)
}
// componentProps
export function initProps(instance, rawProps) {
// 在这里先处理 props 的情况
// 因为某些组件可能没有 props,所以要给一个默认值
instance.props = rawProps || {}
// TODO attrs
}
在调用 component.setup 的时候调用传入 props 参数
function setupStatefulComponent(instance) {
const component = instance.vnode.type
// other code ...
const { setup } = component
if (setup) {
// 在这里将 instance.props 传入
const setupResult = setup(instance.props)
handleSetupResult(instance, setupResult)
}
}
这样我们就可以获取 props上了。
3. props 挂载到 this 中
import { h } from '../../lib/mini-vue.esm.js'
export const Foo = {
setup(props) {
console.log(props)
},
render() {
// props 也会被挂载到 this 中
return h('div', {}, 'counter: ' + this.count)
},
}
还记得之前我们是如何将 setup 返回值挂载到 props 中的嘛?
export const componentPublicInstanceProxyHandlers = {
get({ _: instance }, key) {
const { setupState } = instance
// 在这里挂载 setupState
// 此时我们还可以进行判断
if(key in setupState){
return setupState[key]
}
const publicGetter = PublicProxyGetterMapping[key]
if(publicGetter){
return publicGetter(instance)
}
},
}
export const componentPublicInstanceProxyHandlers = {
get({ _: instance }, key) {
const { setupState, props } = instance
// 在这里也可以对 props 进行判断
if(key in setupState){
return setupState[key]
}else if(key in props) {
return props[key]
}
const publicGetter = PublicProxyGetterMapping[key]
if(publicGetter){
return publicGetter(instance)
}
},
}
那么我们就可以将重复的部分抽离出来了
// shared/index.ts
export function hasOwn(target, key) {
return Reflect.has(target, key)
}
export const componentPublicInstanceProxyHandlers = {
get({ _: instance }, key) {
const { setupState, props } = instance
// 将重复的部分抽离出来
if(hasOwn(setupState, key)){
return Reflect.get(setupState, key)
}else if(hasOwn(props, key)) {
return Reflect.get(setupState, key)
}
const publicGetter = PublicProxyGetterMapping[key]
if(publicGetter){
return publicGetter(instance)
}
},
}
然后我们再次测试一下,发现也是没有问题的
4. props 是 readonly 的
最后,props 是不可以被修改的,那么这个该怎么实现呢?还记得我们在 reactivity 的模块中 readonly 和 shallowReadonly 嘛?这里只需要包一层就可以了,所以需要用 shallowReadonly 来包一层
// component.ts
function setupStatefulComponent(instance) {
// other code ...
// 传入的 setup 用 shallowReadonly 包一层
const setupResult = setup(shallowReadonly(instance.props))
}
我们测试一下
import { h } from '../../lib/mini-vue.esm.js'
export const Foo = {
setup(props) {
console.log(props)
props.count++
console.log(props)
},
render() {
return h('div', {}, 'counter: ' + this.count)
},
}
发现没有问题,props 的值是无法改变的
