【问题标题】:JavaScript component conversion to TypeScript: What is the type of propsJavaScript 组件转换为 TypeScript:props 的类型是什么
【发布时间】:2021-12-07 21:51:09
【问题描述】:

将 VueJS 项目从 JavaScript + Options API 迁移到 TypeScript + Composition API,我逐渐找到了大多数东西的等价物。我正在努力解决的一件事是v-model 功能。我找到了一个good article on implementing it using Composition API,作者在其中创建了一个可组合的函数,该函数可以在想要实现v-model 的组件中重用。我现在正在尝试使用 TypeScript 编写一个等效函数。

这里是原始JS代码:

import { computed } from 'vue'
export function useModelWrapper(props, emit, name = 'modelValue') { 
  return computed({ 
    get: () => props[name], 
    set: (value) => emit(`update:${name}`, value) 
  })
}

我的 TS 实现如下所示:

import { computed, ComputedRef } from '@vue/composition-api'

export function useModelWrapper<T> (props: Record<string, unknown>, emit: (event: string, value: T) => void, name: 'modelValue') : ComputedRef {
  return computed({
    get: () => props[name],
    set: (value) => emit(`update:${name}`, <T>value)
  })
}

这编译得很好,但 Vue 对此不太满意。我明白了

没有重载匹配这个调用

如果我在组件中使用此函数会出错。

我怀疑 TS 版本中 props 的类型不正确,但我无法弄清楚我应该在那里使用什么类型。我已经用Object、unknown 和any 进行了尝试,但它们都不允许我在getter 中执行props[name](any 可以,但是 TS 抱怨我不应该使用any 作为props 的类型)。

我在这里做错了什么?

编辑

这是完整的错误文本:

{
    "resource": "../ProjectsDropdown.vue",
    "owner": "_generated_diagnostic_collection_name_#6",
    "code": "2769",
    "severity": 8,
    "message": "No overload matches this call.
Overload 1 of 3, '(options: ComponentOptionsWithoutProps<unknown, unknown, Data, {}, {}>): VueProxy<unknown, unknown, Data, {}, {}>', gave the following error.
Type '{ modelValue: PropType<Project>; }' is not assignable to type 'undefined'.
Overload 2 of 3, '(options: ComponentOptionsWithArrayProps<string, Data, Data, {}, {}, Readonly<{ [x: string]: any; }>>): VueProxy<Readonly<{ [x: string]: any; }>, Data, Data, {}, {}>', gave the following error.
Type '{ modelValue: PropType<Project>; }' is not assignable to type 'string[]'.
  Object literal may only specify known properties, and 'modelValue' does not exist in type 'string[]'.
Overload 3 of 3, '(options: ComponentOptionsWithProps<ComponentPropsOptions<Data>, Data, Data, {}, {}, ({ [x: number]: string; } & { [iterator]?: IterableIterator<string> | undefined; ... 32 more ...; 
toLocaleString?: string | undefined; }) | ({} & { ...; })>): VueProxy<...>', gave the following error.
Type '{ modelValue: PropType<Project>; }' is not assignable to type 'ComponentPropsOptions<Data> | undefined'.
  Types of property 'modelValue' are incompatible.
    Type 'PropType<Project>' is not assignable to type 'Prop<unknown, unknown> | null | undefined'.
      Type 'new (...args: never[]) => Project & object' is not assignable to type 'Prop<unknown, unknown> | null | undefined'.
        Type 'new (...args: never[]) => Project & object' is not assignable to type 'new (...args: string[]) => Function'.
          Types of parameters 'args' and 'args' are incompatible.
            Type 'string' is not assignable to type 'never'.",
    "source": "Vetur",
    "startLineNumber": 59,
    "startColumn": 16,
    "endLineNumber": 119,
    "endColumn": 3
}

编辑 2

这是我在组件中使用此函数的方式:

<script lang='ts'>
export default defineComponent({
  ...
  import { useModelWrapper } from '../usemodelWrapper'
  props: {
    modelValue: Object as PropType<Project>
  },
  setup (props, { emit }) {
  return {
    selectedProject: useModelWrapper<Project>(props, emit, 'modelValue')
  }
})
</script>

【问题讨论】:

  • 这个方法是怎么调用的?
  • @Eldar: props 部分有一个属性modelValue: Object as PropType&lt;Project&gt; 并且setup() 函数返回的对象有一个成员selectedProject: useModelWrapper(props, emit, 'modelValue')
  • 我认为问题出在, name: 'modelValue') 部分。这是一个类型声明而不是默认值赋值。试试看name = 'modelValue')
  • @Eldar:谢谢。试过了。仍然得到同样的错误。
  • 请用您使用它的代码更新问题。这是最重要的部分,因为它会导致错误。如果您像评论中显示的那样使用它,问题是您没有使用泛型

标签: javascript typescript vue.js vue-composition-api


【解决方案1】:

以防万一它对某人有所帮助,结果证明我在组件中使用了错误版本的 PropType 来定义我的 v-model 属性。真正的PropType 类应该这样导入:

import { PropType } from '@vue/composition-api'

而不是:

import Vue, { PropType } from 'vue'

编辑

如果对任何人有帮助,以下是我最终使用 Composition API 和 TypeScript 实现强类型 v-model 的方法:

import { computed, WritableComputedRef } from '@vue/composition-api'

export interface IIndexable<T> { [key: string]: T }

export function useModelWrapper<T> (props: IIndexable<T>, emit: (event: string, value: T) => void, name = 'modelValue') : WritableComputedRef<T> {
  return computed<T>({
    get: () => props[name] as T,
    set: (value: T) => emit('input', value as T)
  })
}

然后你可以像这样将它导入到任何组件中:

export default defineComponent({
  name: 'ProjectsDropdown',
  props: {
    value: Object as PropType<Project>
  },
  setup (props, { emit }) {
    const selectedProject = useModelWrapper<Project>(props, emit, 'value')

    return { selectedProject }
  }
}

并将其绑定到组件模板中要绑定的任何内容。

请注意,这适用于 Vue 2.x。对于 Vue 3,您需要在上面的代码中将 value 属性的名称更改为 modelValue 和 emit('input', value as T) 和 emit('update:modelValue', value)。

编辑 2

请参阅下面@emeraldsanto 的回答。它提供了更好的实现。

【讨论】:

    【解决方案2】:

    看到你自己的答案后,我觉得这可能会更严格。

    import { computed, WritableComputedRef } from '@vue/composition-api'
    
    export function useModelWrapper<TProps, TKey extends keyof TProps> (
      props: TProps,
      emit: (event: string, value: TProps[TKey]) => void,
      name: TKey = 'modelValue' as TKey
    ) : WritableComputedRef<TProps[TKey]> {
      return computed<TProps[TKey]>({
        get: () => props[name],
        set: (value: TProps[TKey]) => emit('input', value)
      })
    }
    

    我没有专门用 Vue 对其进行测试,但是该函数应该限制您可以将哪个键作为第三个参数传递,并根据传递的 name 而不是 @987654323 的所有值的联合正确推断返回类型@。

    【讨论】:

    • 是的。这有效并正确推断类型。为了完整起见,我将添加缺少的导入并接受它作为答案。
    猜你喜欢
    • 2016-04-02
    • 2011-03-19
    • 2014-03-21
    • 2016-11-04
    • 1970-01-01
    • 2019-04-14
    • 2016-03-14
    • 2011-03-06
    • 2019-03-23
    相关资源
    最近更新 更多