我有一个具有字符串和数字属性值的对象。当我想设置某些字段的值时,TypeScript在使用indexOf
方法进行检查时无法检查属性类型。
以下是示例:
type TSomeObject = {
title: string,
description: string,
count: number
}
const someFunction = (myObj: TSomeObject, field: keyof TSomeObject, newValue: string): TSomeObject => {
if (field === 'title' || field === 'description') {
myObj[field] = newValue
}
return myObj
}
const otherFunction = (myObj: TSomeObject, field: keyof TSomeObject, newValue: string): TSomeObject =>{
const editableFields = ['title', 'description']
if (editableFields.indexOf(field) >= 0) {
// TypeScript error here
myObj[field] = newValue
}
return myObj
}
请参见TS playground中的示例。
虽然someFunction
正常工作,但otherFunction
失败并出现TypeScript错误:
“字符串”类型不能分配为“从不”类型。
我知道它与count
类型的number
属性有关,但是我检查了我正在编辑哪个字段。我在这里做什么错了?
答案 0 :(得分:1)
这里发生了一些事情。
一个是editableFields
被推断为string[]
,对于您的目的而言太宽了。从TS3.4开始,您可以使用const
assertion来推断文字的较窄类型:
const editableFields = ['title', 'description'] as const;
// const editableFields: readonly ["title", "description"]
现在editableFields
是一个元组,其成员已知为"title"
和"description"
。
接下来,TypeScript不会将arr.indexOf(x) >= 0
的结果作为x
类型的type guard。因此,即使测试为true
,x
的类型也不会自动缩小为typeof arr[number]
。这只是TypeScript的限制;并非编译器可以检测到每种可能的测试方式。幸运的是,TypeScript使您能够制作user-defined type guard functions。在这种情况下,您可能想要做这样的事情:
declare function arrayContains<T extends U, U>(
haystack: ReadonlyArray<T>,
needle: U
): needle is T;
(我使用的是ReadonlyArray
而不是Array
,因为它更通用。每个Array
都是ReadonlyArray
,反之亦然。是的,这是一个奇怪的名字,因为ReadonlyArray
可能不是只读的;也许ReadonlyArray
应该是DefinitelyReadableButNotNecessarilyWritableArray
,而Array
应该是DefinitelyBothReadableAndWritableArray
。也许不是。)
这是一个通用的用户定义类型防护,它采用haystack
类型的Array<T>
数组和比{{宽的类型needle
的{{1}} 1}},如果返回U
,则编译器会理解为T
。 (还 认为true
意味着needle is T
不是 false
,might not always be desirable。在您的情况下,因为needle
和T
都是字符串文字或它们的并集,所以这不是问题。)
如果我们有T
的实现,则将测试更改为:
U
因此,让我们实现arrayContains()
。
这是下一件事:TypeScript's standard library's typings for indexOf()
是这样:
if (arrayContains(editableFields, field)) {
myObj[field] = newValue; // no error now
}
这意味着arrayContains()
必须与interface ReadonlyArray<T> {
indexOf(searchElement: T, fromIndex?: number): number;
}
的元素具有相同的类型(needle
)。但是,您希望T
是 wide 类型(您要查看haystack
是否在needle
元素数组中)。因此,TypeScript不允许您这样做:
string
幸运的是,您可以安全地将"this" | "that"
扩展为function arrayContains<T extends U, U>(haystack: ReadonlyArray<T>, needle: U): needle is T {
return haystack.indexOf(needle) >= 0; // error!
// ~~~~~~ <-- U is not assignable to T
}
,因此可以这样写:
ReadonlyArray<T>
那行得通!相反,您也可以使用type assertion使编译器静音,如下所示:
ReadonlyArray<U>
但是我通常尝试避免断言,除非它们是必要的,而在这里则不是。由你决定。
因此,我们将它们放在一起:
function arrayContains<T extends U, U>(haystack: ReadonlyArray<T>, needle: U): needle is T {
const widenedHaystack: ReadonlyArray<U> = haystack;
return widenedHaystack.indexOf(needle) >= 0;
}
好的,希望能有所帮助。祝你好运!