如何使用严格类型的有效负载发出事件? | Vue 3 组合 API + TypeScript
Posted
技术标签:
【中文标题】如何使用严格类型的有效负载发出事件? | Vue 3 组合 API + TypeScript【英文标题】:How to emit events with a strictly typed payload? | Vue 3 Composition API + TypeScript 【发布时间】:2021-07-09 15:34:54 【问题描述】:我正在尝试使用 TypeScript 学习 Vue 3 组合 API,特别是如何使用严格类型化的有效负载发出事件。
我在下面有一个示例,但我不确定它是否正确。所以我的问题是,是否有任何其他方法可以发出具有严格类型负载的事件?
示例
我使用了这个包:https://www.npmjs.com/package/vue-typed-emit 并让它与下面的示例一起使用,其中我将一个布尔值从子组件传递给父组件:
子组件:
<script lang="ts">
import defineComponent, ref, watch from 'vue'
import CompositionAPIEmit from 'vue-typed-emit'
interface ShowNavValue
showNavValue: boolean
interface ShowNavValueEmit
emit: CompositionAPIEmit<ShowNavValue>
export default defineComponent(
name: 'Child',
emits: ['showNavValue'],
setup(_: boolean, emit : ShowNavValueEmit)
let showNav = ref<boolean>(false)
watch(showNav, (val: boolean) =>
emit('showNavValue', val)
)
return
showNav
)
</script>
父组件
<template>
<div id="app">
<Child @showNavValue="toggleBlurApp" />
<div :class="'blur-content': blurApp"></div>
</div>
</template>
<script lang="ts">
import defineComponent, ref from 'vue';
import Child from './components/Child.vue';
export default defineComponent(
name: 'Parent',
components:
Child
,
setup()
let blurApp = ref<boolean>(false);
let toggleBlurApp = (val: boolean) =>
blurApp.value = val;
return
blurApp,
toggleBlurApp
);
</script>
<style lang="scss">
.blur-content
filter: blur(5px);
transition : filter .2s linear;
</style>
【问题讨论】:
【参考方案1】:安装 vue-typed-emit 是不必要的,可以用这个方法代替:首先你可以定义你希望你的事件符合的接口,其中事件键是“事件”,类型是事件的发出输入“参数”。
interface Events
foo?: string;
bar: number;
baz: a: string, b: number ;
然后,您可以从 vue 导入并使用现有的 SetupContext 接口,并定义其扩展,并增加对发出函数参数的限制。
interface SetupContextExtended<Event extends Record<string, any>> extends SetupContext
emit: <Key extends keyof Event>(event: Key, payload: Event[Key]) => void;
此接口实质上用一个 emit 函数替换了现有的 emit(event: string, args: any) => void
,该函数接受 'event' 作为 'Events' 接口的键,其对应类型为 'args'。
我们现在可以在组件中定义我们的 setup 函数,将 SetupContext 替换为 SetupContextExtended 并传入“Events”接口。
setup(props, context: SetupContextExtended<Events>)
context.emit('foo', 1); // TypeError - 1 should be string
context.emit('update', 'hello'); // TypeError - 'update' does not exist on type Events
context.emit('foo', undefined); // Success
context.emit('baz', a: '', b: 0 ); // Success
工作组件:
<script lang="ts">
import defineComponent, SetupContext from 'vue';
interface Events
foo?: string;
bar: number;
baz: a: string, b: number ;
interface SetupContextExtended<Event extends Record<string, any>> extends SetupContext
emit: <Key extends keyof Event>(event: Key, payload: Event[Key]) => void;
export default defineComponent(
name: 'MyComponent',
setup(props, context: SetupContextExtended<Events>)
context.emit('foo', 1); // TypeError - 1 should be string
context.emit('update', 'hello'); // TypeError - 'update' does not exist on type Events
context.emit('foo', undefined); // Success
context.emit('baz', a: '', b: 0 ); // Success
);
</script>
现在让这个扩展类型在所有现有和未来的组件中都可用 - 然后你可以扩充 vue 模块本身,在你现有的导入中包含这个自定义的 SetupContextExtended 接口。对于此示例,它已添加到 shims-vue.d.ts 中,但如果需要,您应该能够将其添加到专用文件中。
// shims-vue.d.ts
import * as vue from 'vue';
// Existing stuff
declare module '*.vue'
import type DefineComponent from 'vue';
const component: DefineComponent<, , any>;
export default component;
declare module 'vue'
export interface SetupContextExtended<Event extends Record<string, any>> extends vue.SetupContext
emit: <Key extends keyof Event>(event: Key, payload: Event[Key]) => void;
带有增强的 vue 模块的最终组件:
<script lang="ts">
import defineComponent, SetupContextExtended from 'vue';
interface Events
foo?: string;
bar: number;
baz: a: string, b: number ;
export default defineComponent(
name: 'MyComponent',
setup(props, context: SetupContextExtended<Events>)
context.emit('baz', a: '', b: 0 ); // Success
);
</script>
使用它我个人在父组件中定义和导出事件接口并将其导入子组件,以便父组件定义管理子组件发出事件的合同
【讨论】:
【参考方案2】:我正在使用<script setup lang="ts">
,并且我正在强烈键入 AND 来验证我的 emit 的有效负载,如下所示:
<script setup lang="ts">
defineEmits(
newIndex(index: number)
return index >= 0
,
)
// const items = [ text: 'some text' , ...]
</script>
然后像这样发出事件:
<template>
<div
v-for="(item, index) in items"
:key="index"
@click="$emit('newIndex', index)"
>
item.text
</div>
</template>
如果我只想声明并键入上面的 emit,我会这样做:
defineEmits<
(event: 'newIndex', index: number): void
>()
【讨论】:
【参考方案3】:Vue <script setup>
用于声明组件已发出的编译器宏
事件。预期参数与组件emits
选项相同。
运行时声明示例:
const emit = defineEmits(['change', 'update'])
基于类型的声明示例:
const emit = defineEmits<
(event: 'change'): void
(event: 'update', id: number): void
>()
emit('change')
emit('update', 1)
这只能在 <script setup>
内部使用,在
输出并且应该不在运行时实际调用。
【讨论】:
以上是关于如何使用严格类型的有效负载发出事件? | Vue 3 组合 API + TypeScript的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Firebase Cloud Messaging 发送图像并使用数据有效负载发出通知