props组件传值应该是很常见的,几乎是每个抽离组件必须使用到的
props
props实现有三个点
1.props可以通过setup这边传入过来
2.使render内的h函数能用this.xxx获取到props的值
3.props是shallowReadonly 类型的只读响应式数据
我们先创建一个新的组件,组件本质就是对象,而且还没实现编译,所以使用app.js的格式创建一个组件就行
Foo.js
export const Foo = {
render() {
return h('div', {}, 'foo:' + this.count)
},
setup(props) {
// props 不可更改
// 使render的h函数内能使用this.count
// shallowReadonly 类型的响应式对象
console.log(props)
props.count++
console.log(props)
}
}
App.js
之前处理过h传入的第一个参数的类型判断及相关处理,所以直接把Foo传进去就行
import { h } from "../../lib/xin-mini-vue.esm.js"
import { Foo } from './Foo.js'
export const App = {
// 没搞编译,所以template不写,写render就行
render() {
// 返回虚拟VNode
return h('div',
{
id: 'root',
class: ['bgc-red', 'font-blue'],
},
[h('div', {}, 'hi,' + this.msg), h(Foo, {
count: 99
})]
)
},
setup() {
// composition api
return {
msg: 'xin-vue'
}
}
}
首先就是实现setup传值的需求
component.ts
我们是在setupStatefulComponent函数中调用的setup,所以传值在setupStatefulComponent调用setup时处理
export function createComponentInstance(vnode) {
const component = {
vnode,
type: vnode.type,
setupState: {},
props: {} // 加上初始值
}
return component
}
// 之前留的TODO不知道大家还记不记得
export function setupComponent(instance) {
const { vnode: { props } } = instance
// TODO
initProps(instance, props)
// initSlots()
setupStatefulComponent(instance)
}
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(shallowReadonly(instance.props)) // 这里直接完成第三个需求
handleSetupResult(instance, setupResult)
}
}
同时完成第三个需求的时候,在 reactive.ts 中的createActiveObject函数在new Proxy的时候,target不是对象时会报错,我们在里面做一层判断即可
reactive.ts
export function createActiveObject(raw, beseHandlers = mutableHandlers) {
if (!isObject(raw)) console.warn('target必须是一个对象')
return new Proxy(raw, beseHandlers)
}
componentProps.ts
我们在开发新功能的时候最好是把所有单独功能抽离出去,看起来不冗余,也更有利于后期维护
这里需要给个空对象,防止 undefined 报错
export function initProps(instance, rawProps) {
// TODO
// attr
// 没有值就给个空对象
instance.props = rawProps || {}
}
剩下就是一个this指向问题,之前实现 组件代理对象 时实现过,所以套用就行
componentPublicInstance.ts
export const publicInstanceProxyHandlers = {
get({ _: instance }, key) {
// setupState
const { setupState, props } = instance
if (key in setupState) {
return Reflect.get(setupState, key)
}
if (hasOwn(setupState, key)) {
return Reflect.get(setupState, key)
}
else if (hasOwn(props, key)) {
return Reflect.get(props, key)
}
const publicGetter = Reflect.get(publicPropertiesMap, key)
if (publicGetter) {
return publicGetter(instance)
}
}
}
这里单独把hasOwn提出去为一个通用工具函数
shared/index.ts
加call同样是this指向问题
export const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key)
结语
this指向问题的实现思路和组件代理对象的思路是一样的,就是看看有没有值,有值就反,没值就加个{}防止报错,下一章就到 实现组件emit功能