如何在打字脚本中有选择地从一个Partial分配到另一个



在下面的TypeScript代码片段中,我需要从一个对象分配到另一个对象,其中两个对象都是Partial<InjectMap>。在这里,我的直觉是打字脚本应该能够理解发生了什么,因为在第(B(行,key的类型是typeof InjectMap。因此,它应该能够正确地分配从inputoutput的值。


export interface InjectMap {
"A": "B",                          // line (A)
"C": "D"
}
type InjectKey = keyof InjectMap;
const input: Partial<InjectMap> = {};
const output: Partial<InjectMap> = {};
const keys: InjectKey[] = []
for (let i = 0; i < keys.length; i++) {
const key = keys[i]                // line (B)
output[key] = input[key]           // line (C)  - Gives Error
}

游乐场链接

但它在第(C(行给出了以下错误:

Type '"B" | "D" | undefined' is not assignable to type 'undefined'.
Type '"B"' is not assignable to type 'undefined'.

奇怪的是,如果我评论第(A(行,错误就会消失。这是TypeScript的一个缺点,还是我遗漏了什么?

我不认为这是一个错误,更改值几乎总是不安全的,TS只是试图使其安全。

让我们从InjectMap接口开始。

很明显,你不能拥有非法状态,比如:

const illegal: InjectMap = {
"A": "D", // expected B
"C": "B" // expected D
}

这很重要。

让我们继续我们的循环:

interface InjectMap {
"A": "B",
"C": "D"
}
type InjectKey = keyof InjectMap;
const input: Partial<InjectMap> = {};
const output: Partial<InjectMap> = {};
const keys: InjectKey[] = []

for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const inp = input[key] // "B" | "D" | undefined
const out = output[key] // "B" | "D" | undefined
output[key] = input[key]

}

由于key是动态的,TS不确定它是BD还是undefined。我希望你同意我的观点,在这个地方inp的正确类型是"B" | "D" | undefined,这是预期的行为,因为类型系统是静态的。

由于inputoutput不被key绑定,TS希望避免非法状态。为了说明这一点,请考虑下一个例子,它与我们的相同

type KeyType_ = "B" | "D" | undefined
let keyB: KeyType_ = 'B';
let keyD: KeyType_ = 'D'
output[keyB] = input[keyD] // Boom, illegal state! Runtime error!

您可能已经注意到,keyBkeyD具有相同的类型,但值不同。

与您在示例中遇到的情况相同,TS无法计算出它只能计算出类型的值。

如果你想让TS高兴,你应该添加条件语句或typeguard:

for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (key === 'A') {
let out = output[key] // "B"
let inp = input[key] //  "B"
output[key] = input[key] // ok
}
if (key === 'C') {
let out = output[key] // "D"
let inp = input[key] //  "D"
output[key] = input[key] // ok
}
}

请记住,当你改变你的价值观时,你就会失去类型保证。

看看这个和这个关于突变的问题。

提香·德拉戈米尔·切尔尼科娃的这段话也很不错。

这里有一个不安全突变的例子,取自@Titian的演讲:

type Type = {
name: string
}
type SubTypeA = Type & {
salary: string
}
type SubTypeB = Type & {
car: boolean
}
type Extends<T, U> =
T extends U ? true : false

let employee: SubTypeA = {
name: 'John Doe',
salary: '1000$'
}
let human: Type = {
name: 'Morgan Freeman'
}
let director: SubTypeB = {
name: 'Will',
car: true
}

// same direction
type Covariance<T> = {
box: T
}
let employeeInBox: Covariance<SubTypeA> = {
box: employee
}
let humanInBox: Covariance<Type> = {
box: human
}
// Mutation ob object property
let test: Covariance<Type> = employeeInBox
test.box = director // mutation of employeeInBox
const result_ = employeeInBox.box.salary // while result_ is undefined, it is infered a a string

// Mutation of Array
let array: Array<Type> = []
let employees = [employee]
array = employees
array.push(director)
const result = employees.map(elem => elem.salary) // while salary is [string, undefined], is is infered as a string[]
console.log({result_,result})

游乐场

如何修复

请告诉我它是否适合你:


export interface InjectMap {
"A": "B",
"C": "D"
}
const assign = <Input extends InjectMap, Output extends InjectMap>(
input: Partial<Input>,
output: Partial<Output>,
keys: Array<keyof InjectMap>
) => keys.reduce((acc, elem) => ({
...acc,
[elem]: input[elem]
}), output)

游乐场

更新

为什么此分析适用于[elem]:input[elem]?输入[elem]可以再次是"0";B"|"D"|未定义,因此编译器应该再次出错。但是,在这里,编译器很聪明地知道输入[elem]的类型适用于[elem]。有什么区别?

这是一个很好的问题。

当你用计算键创建新对象时,TS会使这个对象成为indexed by string,我的意思是,你可以使用任何想要的字符串prop

const computedProperty = (prop: keyof InjectMap) => {
const result = {
[prop]: 'some prop' //  { [x: string]: string; }
}
return result
}

它给你更多的自由,但也提供了一点不安全。

拥有强大的权力,就肩负着巨大的责任

因为现在,不幸的是,你可以这样做:

const assign = <Input extends InjectMap, Output extends InjectMap>(
input: Partial<Input>,
output: Partial<Output>,
keys: Array<keyof InjectMap>
) => keys.reduce((acc, elem) => {  
return {
...acc,
[elem]: 1 // unsafe behavior
}
}, output)

正如您可能已经注意到的,assign函数的返回类型是Partial<Output>,这不是真的。

因此,为了使其完全类型安全,您可以使用typeguard,但我认为这会使过于复杂

最新更新