JavaScript 组件到 TypeScript 的转换:props 的类型是什么
JavaScript component conversion to TypeScript: What is the type of props
将 VueJS 项目从 JavaScript + 选项 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 对此不太满意。我得到
No overload matches this call
如果我在组件中使用这个函数会出错。
我怀疑是TS版本中props
的类型不对,但我没弄清楚应该用什么类型。我已经尝试使用 Object
、unknown
和 any
,但是其中的 none 允许我在 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>
以防万一它能帮助别人,结果我在组件中使用了错误版本的 PropType
来定义我的 v-model
道具。真正的PropType
class应该这样导入:
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
prop 的名称更改为 modelValue
和 emit('input', value as T)
以及 emit('update:modelValue', value)
。
编辑 2
请参阅下面@emeraldsanto 的回答。它提供了更好的实现。
看了你自己的回答,感觉还可以再严一点
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
而不是正确地推断 return 类型props
.
的所有值的并集
这是@emeraldsanto 答案的扩展,但类型推断是 Vue 3 组件发出的字符串
export function useModelWrapper<TProps extends Record<string, unknown> | { modelValue: unknown },
TKey extends keyof TProps & string = 'modelValue',
TEmit extends (event: `update:${TKey}`, value: TProps[TKey]) => void = (event: `update:${TKey}`, value: TProps[TKey]) => void > (
props: TProps,
emit: TEmit,
name: TKey = 'modelValue' as TKey
) : WritableComputedRef<TProps[TKey]> {
return computed<TProps[TKey]>({
get: () => props[name],
set: (value: TProps[TKey]) => emit(`update:${name}` as `update:${TKey}`, value)
})
}
使用喜欢
const props = defineProps<{
modelValue: string
foo?: number
}> ()
const emit = defineEmits<{
(e: 'update:modelValue', id: string): void
(e: 'update:foo', value: number | undefined): void
}> ()
const value = useModelWrapper(props, emit)
const fooValue = useModelWrapper(props, emit, 'foo')
将 VueJS 项目从 JavaScript + 选项 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 对此不太满意。我得到
No overload matches this call
如果我在组件中使用这个函数会出错。
我怀疑是TS版本中props
的类型不对,但我没弄清楚应该用什么类型。我已经尝试使用 Object
、unknown
和 any
,但是其中的 none 允许我在 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>
以防万一它能帮助别人,结果我在组件中使用了错误版本的 PropType
来定义我的 v-model
道具。真正的PropType
class应该这样导入:
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
prop 的名称更改为 modelValue
和 emit('input', value as T)
以及 emit('update:modelValue', value)
。
编辑 2
请参阅下面@emeraldsanto 的回答。它提供了更好的实现。
看了你自己的回答,感觉还可以再严一点
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
而不是正确地推断 return 类型props
.
这是@emeraldsanto 答案的扩展,但类型推断是 Vue 3 组件发出的字符串
export function useModelWrapper<TProps extends Record<string, unknown> | { modelValue: unknown },
TKey extends keyof TProps & string = 'modelValue',
TEmit extends (event: `update:${TKey}`, value: TProps[TKey]) => void = (event: `update:${TKey}`, value: TProps[TKey]) => void > (
props: TProps,
emit: TEmit,
name: TKey = 'modelValue' as TKey
) : WritableComputedRef<TProps[TKey]> {
return computed<TProps[TKey]>({
get: () => props[name],
set: (value: TProps[TKey]) => emit(`update:${name}` as `update:${TKey}`, value)
})
}
使用喜欢
const props = defineProps<{
modelValue: string
foo?: number
}> ()
const emit = defineEmits<{
(e: 'update:modelValue', id: string): void
(e: 'update:foo', value: number | undefined): void
}> ()
const value = useModelWrapper(props, emit)
const fooValue = useModelWrapper(props, emit, 'foo')