typescript 如何使用严格类型的有效负载发出事件?|Vue 3合成API +类型脚本

ujv3wf0j  于 2023-03-09  发布在  TypeScript
关注(0)|答案(4)|浏览(144)

我正在尝试学习使用TypeScript的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>
sqougxex

sqougxex1#

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>中使用,在输出中编译掉,并且应该在运行时实际调用。

kyks70gy

kyks70gy2#

安装vue-typed-emit是不必要的,可以使用以下方法替换:首先,您可以定义您希望事件符合的接口,其中事件键是“event”,类型是事件的发出类型“args”。

interface Events {
    foo?: string;
    bar: number;
    baz: { a: string, b: number };
}

然后,您可以从vue导入并使用现有的SetupContext接口,并定义一个扩展,对emit函数参数添加限制。

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”。
我们现在可以在组件中定义设置函数,用SetupContextExtended替换SetupContext,并传入'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>

我使用这个接口在父组件中定义并导出Events接口,然后将其导入到子组件中,这样父组件就定义了控制子组件的发射事件的契约

5kgi1eie

5kgi1eie3#

如果你使用的是<script setup>,那么使用defineEmits(正如guangzan的回答所建议的那样)就可以了。官方文档的链接在这里:https://vuejs.org/guide/typescript/composition-api.html#typing-component-emits
但是,如果您喜欢使用<script lang="ts">setup函数,那么基本上有两个选项可以指定强类型事件有效负载。

  • 选项1 *

请看Kiaan Edge-Ford给出的精彩答案。如果您想在多个组件上重复使用并强制执行相同的事件,此选项可能会很合适。

  • 选项#2*(较少代码)

官方的Vue文档目前还不清楚,但emits:实际上已经有了一种额外的形式,它可以是函数的对象,而不是字符串数组,所以可以这样翻译:

export default defineComponent({
  props: ...
  emits: ['set-field', 'update:is-valid']
  ...

变成了这样

export default defineComponent({
  props: ...
  emits: {
    // eslint-disable-next-line unused-imports/no-unused-vars, no-useless-computed-key, object-shorthand
    ['set-field'](payload: { partA: number, partB: string, partC: boolean }) { return true; },
    // eslint-disable-next-line unused-imports/no-unused-vars, no-useless-computed-key, object-shorthand
    ['update:is-valid'](payload: boolean) { return true; },
  },
  ...

您可能需要也可能不需要eslint注解来防止警告。这里的函数是一个验证函数,所以return true在这里的意思是:始终有效。
现在,当关联的emit(...)函数没有发送正确的有效负载时,您的环境应该会发出警告。

tct7dpnv

tct7dpnv4#

我正在使用<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
}>()

相关问题