Emit の実装
開発者インターフェース
props に引き続き emit の実装をしていきます.
emit の実装は比較的ライトなのですぐに終わります.
開発者インタフェース的には emit は setup 関数の第 2 引数から受け取れるような形にします.
ts
const MyComponent: Component = {
props: { someMessage: { type: String } },
setup(props: any, { emit }: any) {
return () =>
h('div', {}, [
h('p', {}, [`someMessage: ${props.someMessage}`]),
h('button', { onClick: () => emit('click:change-message') }, [
'change message',
]),
])
},
}
const app = createApp({
setup() {
const state = reactive({ message: 'hello' })
const changeMessage = () => {
state.message += '!'
}
return () =>
h('div', { id: 'my-app' }, [
h(
MyComponent,
{
'some-message': state.message,
'onClick:change-message': changeMessage,
},
[],
),
])
},
})
実装
props の時と同じように,~/packages/runtime-core/componentEmits.ts
というファイルを作成してそこに実装していきます.
emit は単純に,instance に emit 用の関数を実装し,実行時は vnode が持つ props からハンドラを探し実行します.
~/packages/runtime-core/componentEmits.ts
ts
export function emit(
instance: ComponentInternalInstance,
event: string,
...rawArgs: any[]
) {
const props = instance.vnode.props || {}
let args = rawArgs
let handler =
props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))]
if (handler) handler(...args)
}
~/packages/shared/general.ts
ts
export const capitalize = (str: string) =>
str.charAt(0).toUpperCase() + str.slice(1)
export const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``)
~/packages/runtime-core/component.ts
ts
export interface ComponentInternalInstance {
// .
// .
// .
emit: (event: string, ...args: any[]) => void
}
export function createComponentInstance(
vnode: VNode,
): ComponentInternalInstance {
const type = vnode.type as Component
const instance: ComponentInternalInstance = {
// .
// .
// .
emit: null!, // to be set immediately
}
instance.emit = emit.bind(null, instance)
return instance
}
これを setup 関数に渡してあげれば OK です.
~/packages/runtime-core/componentOptions.ts
ts
export type ComponentOptions = {
props?: Record<string, any>
setup?: (
props: Record<string, any>,
ctx: { emit: (event: string, ...args: any[]) => void },
) => Function // ctx.emitを受け取れるように
render?: Function
}
ts
const mountComponent = (initialVNode: VNode, container: RendererElement) => {
const instance: ComponentInternalInstance = (initialVNode.component =
createComponentInstance(initialVNode));
const { props } = instance.vnode;
initProps(instance, props);
const component = initialVNode.type as Component;
if (component.setup) {
// emitを渡してあげる
instance.render = component.setup(instance.props, {
emit: instance.emit,
}) as InternalRenderFunction;
}
先ほど想定していた開発者インタフェースの例で動作を確認してみましょう!
ちゃんと動いていればこれで props/emit によるコンポーネント間のやりとりが行えるようになりました!
ここまでのソースコード:
chibivue (GitHub)