TypeScript无法正确检查对象属性

时间:2019-10-10 22:38:07

标签: typescript

我有一个具有字符串和数字属性值的对象。当我想设置某些字段的值时,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属性有关,但是我检查了我正在编辑哪个字段。我在这里做什么错了?

1 个答案:

答案 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。因此,即使测试为truex的类型也不会自动缩小为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不是 falsemight not always be desirable。在您的情况下,因为needleT都是字符串文字或它们的并集,所以这不是问题。)

如果我们有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; 
}

好的,希望能有所帮助。祝你好运!

Link to code