如何使用严格类型的有效负载发出事件? | 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) =&gt; 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】:

我正在使用&lt;script setup lang="ts"&gt;,并且我正在强烈键入 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 &lt;script setup&gt; 用于声明组件已发出的编译器宏 事件。预期参数与组件emits 选项相同。

运行时声明示例:

const emit = defineEmits(['change', 'update'])

基于类型的声明示例:

const emit = defineEmits<
  (event: 'change'): void
  (event: 'update', id: number): void
>()

emit('change')
emit('update', 1)

这只能在 &lt;script setup&gt; 内部使用,在 输出并且应该在运行时实际调用。

【讨论】:

以上是关于如何使用严格类型的有效负载发出事件? | Vue 3 组合 API + TypeScript的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Firebase Cloud Messaging 发送图像并使用数据有效负载发出通知

严格验证可空字段[重复]

如何在 vue3 中的“setup”方法中“发出”事件?

如何在 Vue 3 组件中使用 Vuex 4 状态数据作为另一个调度的有效负载?

如何在 Vue 中向父布局槽发出事件?

Vue.js - 如何在数据对象更改时向父组件发出?