typescript - 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
type 的属性有关number
,但是我检查了我正在编辑哪个字段。我在这里做错了什么?
解决方案
这里正在发生一些事情。
一个是editableFields
推断为string[]
,这对于您的目的来说太宽了。从 TS3.4 开始,您可以使用const
断言来推断更窄的文字类型:
const editableFields = ['title', 'description'] as const;
// const editableFields: readonly ["title", "description"]
NoweditableFields
是一个元组,其成员已知为"title"
和"description"
。
接下来, TypeScript不会将 的结果arr.indexOf(x) >= 0
作为. 所以即使测试是, 的类型也不会自动缩小。这只是 TypeScript 的一个限制;并非所有可能的测试方法都可以被编译器检测到。幸运的是,TypeScript 使您能够创建用户定义的类型保护函数。在这种情况下,您可能想要做这样的事情:x
true
x
typeof arr[number]
declare function arrayContains<T extends U, U>(
haystack: ReadonlyArray<T>,
needle: U
): needle is T;
(我使用ReadonlyArray
而不是Array
因为它更通用。每个Array
都是 aReadonlyArray
但反之则不然。是的,这是一个奇怪的名称,因为ReadonlyArray
可能不是只读的;也许ReadonlyArray
应该是DefinitelyReadableButNotNecessarilyWritableArray
并且Array
应该是DefinitelyBothReadableAndWritableArray
。也许不是。)
那是一个通用的用户定义的类型保护,它接受haystack
某种类型的数组Array<T>
,以及某种比 更宽needle
的类型,如果它返回,编译器会理解它。(它还认为这意味着is not,这可能并不总是可取的。在您的情况下,因为and都将是字符串文字或它们的联合,所以这不是问题。)U
T
true
needle is T
false
needle
T
T
U
如果我们有一个实现arrayContains()
,我们会将测试更改为:
if (arrayContains(editableFields, field)) {
myObj[field] = newValue; // no error now
}
所以,让我们实现arrayContains()
.
接下来是:TypeScript 的标准库的indexOf()
类型是这样的:
interface ReadonlyArray<T> {
indexOf(searchElement: T, fromIndex?: number): number;
}
这意味着needle
需要T
与haystack
. 但是您希望您needle
的类型更广泛(您正在查看 a是否在元素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
}
幸运的是,您可以安全地将 aReadonlyArray<T>
扩展为 a 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;
}
那行得通!相反,您也可以只使用类型断言来使编译器静音,如下所示:
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
}
好的,希望有帮助。祝你好运!
推荐阅读
- qt - 如何在 QML 中的 Listview 中保留特定列表项之间的空间
- ansible - 如何从dict中设置多个事实?
- java - 为什么在我的方法 onActivityResult 中它没有达到 else if 条件。我想使用 UCrop 将裁剪后的图像保存到 Firebase
- c - 我应该如何制作一个在元素周期表中添加新元素的函数?
- algorithm - 找到包含无向图中给定边的欧拉路径的最小成本(算法)
- python - 使用 python 找到 N^(1/17)
- python - 从充满文件的目录中提取字符串
- azure - 无效或未知密钥:network_security_group_id
- javascript - 如何切换侧边栏?
- node.js - 如何从 Google Maps Direction API 响应中渲染地图?