props
需求
- 从
setup中传入 - 在
render中可以通过this来访问props的值 props第一层不可以被修改,内部深层次不做处理
场景
// App.js
export const App = {
render() {
return h(
'div',
{},
[
h('div', {}, 'hello, ' + this.name),
// 创建 Foo 组件,向其中传入 count
h(Foo, { count: 1 })
]
)
},
setup() {
return {
name: 'mini-vue3'
}
}
}
// Foo.js
export const Foo = {
setup(props) {
// props 对象是只读的,但不是深度只读的
props.count++
console.log(props.count)
},
render() {
// 在 通过 this 获取 props 中的 count
return h('div', {}, 'foo: ' + this.count)
}
}
实现代码
在setupComponent初始化props
// component.ts
export function setupComponent(instance) {
initProps(instance, instance.vnode.props)
}
// componentProps.ts
export function initProps(instance, rawProps) {
// 根组件 App 的 props 是 undefined,所以需要判断一下
instance.props = rawProps || {}
}
在调用setup的时候引入props,用shallowReadonly处理props
// component.ts
export function setupStatefulComponent(instance) {
...
if (setup) {
const setupResult = setup(shallowReadonly(instance.props))
handleSetupResult(instance, setupResult)
}
}
在获取setup返回值的方法PublicInstanceProxyHandlers中处理props返回值
// componentPublicInstance.ts
export const PublicInstanceHandlers = {
get({ _: instance }, key) {
const { setupState, props } = instance
const hasOwn = () => Object.prototype.hasOwnProperty.call(val, key)
if (hasOwn(setupState, key)) {
return setupState[key]
} else if (hasOwn(props, key)) {
return props[key]
}
...
}
emit
需求
- 在
setup第二个参数context对象中导出 - 在子组件发射事件,由父组件接收。例如子组件有一个
emit('bar')事件,父组件通过onBar接收
场景
// Foo.js
export const Foo = {
setup(props, { emit }) {
const emitAdd = () => {
console.log("emmit add");
emit("add", 1, 2);
emit("add-foo");
};
return {
emitAdd,
};
},
render() {
const btn = h(
"button",
{
onClick: this.emitAdd,
},
"emitAdd"
);
const foo = h("p", {}, "foo");
return h("div", {}, [foo, btn]);
},
};
// App.js
export const App = {
render() {
return h("div", {}, [
h("div", {}, "App"),
h(Foo, {
onAdd(a, b) {
console.log("onAdd", a, b);
},
onAddFoo() {
console.log("onAddFoo");
},
}),
]);
},
setup() {
return {
msg: "mini-vue",
};
},
};
实现代码
在setup中传入一个对象,这个对象包含emit
// component.ts
import emit from './componentEmit'
export function createComponentInstance(vnode) {
const component = {
vnode,
type: vnode.type,
setupState: {},
props: {},
emit: () => {},
}
// 将组件实例作为参数绑定在 emit 上,这样用户调用 emit 不需要传 instance,只用传事件名
component.emit = emit.bind(null, component) as any
return component
}
function setupStatefulComponent(instance) {
...
if (setup) {
const setupResult = setup(shallowReadonly(instance.props), {
emit: instance.emit,
})
}
}
调用emit的时候可以传入一个event
export function emit(instance, event, ...args) {
const { props } = instance
const handlerName = toHandlerKey(camelize(event))
const handler = props[handlerName]
handler && handler(...args)
}
// src/shared/index.ts
// 将带 - 的字符串转换为驼峰式 add-foo -> addFoo
export const camelize = (str: string) => {
return str.replace(/-(\w)/g, (_, c: string) => {
return c ? c.toUpperCase() : ''
})
}
// 将字符串首字母转换为大写
export const capitalize = (str: string) => {
return str.charAt(0).toUpperCase() + str.slice(1)
}
// 在字符串之前加上 on
export const toHandlerKey = (str: string) => {
return str ? 'on' + capitalize(str) : ''
}