【问题标题】:TypeScript does not properly check object propertiesTypeScript 无法正确检查对象属性
【发布时间】:2020-02-08 10:26:12
【问题描述】:

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

【问题讨论】:

  • editableFields的类型是string[]。我尝试设置正确的类型,例如: const editableFields: Array = ['title', 'description'],但这只会导致 indexOf 不起作用。
  • 那个游乐场链接不起作用
  • @jcalz Playground 链接现已修复。

标签: typescript


【解决方案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 的结果视为type guard 类型上的x。所以即使测试是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 可能不会被读取-only;也许ReadonlyArray 应该是DefinitelyReadableButNotNecessarilyWritableArrayArray 应该是DefinitelyBothReadableAndWritableArray。也许不是。)

这是一个通用的用户定义类型保护,它采用某种类型的haystack 数组Array&lt;T&gt;,以及某种类型的needle U,它比T 宽,如果它返回@987654355 @,编译器理解为needle is T。 (它认为false暗示needle不是Tmight not always be desirable。在你的情况下,因为TU都将是字符串文字或它们的联合,这不是问题。)

如果我们有 arrayContains() 的实现,我们会将测试更改为:

if (arrayContains(editableFields, field)) {
    myObj[field] = newValue; // no error now
}

那么,让我们实现arrayContains()


接下来是:TypeScript's standard library's typings for indexOf()是这个:

interface ReadonlyArray<T> {
  indexOf(searchElement: T, fromIndex?: number): number;
}

这意味着needle 需要与haystack 的元素属于同一类型(T)。但是您希望您的needle 属于更宽 类型(您正在查看string 是否在"this" | "that" 元素的数组中)。所以 TypeScript 不会让你这样做:

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&lt;T&gt; 扩展为 ReadonlyArray&lt;U&gt;,因此您可以这样写:

function arrayContains<T extends U, U>(haystack: ReadonlyArray<T>, needle: U): needle is T {
    const widenedHaystack: ReadonlyArray<U> = haystack;
    return widenedHaystack.indexOf(needle) >= 0; 
}

这行得通!相反,您也可以使用 type assertion 使编译器静音,如下所示:

function arrayContains<T extends U, U>(haystack: ReadonlyArray<T>, needle: U): needle is T {
    return haystack.indexOf(needle as T) >= 0; // assert
}

但我通常会尽量避免断言,除非它们是必要的,而在这里它们不是。这取决于你。


让我们把它们放在一起:

function arrayContains<T extends U, U>(haystack: ReadonlyArray<T>, needle: U): needle is T {
    const widenedHaystack: ReadonlyArray<U> = haystack;
    return widenedHaystack.indexOf(needle) >= 0; 
}

function otherFunction(myObj: TSomeObject, field: keyof TSomeObject, newValue: string): TSomeObject {
    const editableFields = ['title', 'description'] as const;
    if (arrayContains(editableFields, field)) {
        myObj[field] = newValue
    }
    return myObj
}

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

Link to code

【讨论】:

  • 很好的答案!谢谢
猜你喜欢
  • 1970-01-01
  • 2023-03-28
  • 2018-12-02
  • 1970-01-01
  • 1970-01-01
  • 2017-05-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多