为什么打字稿无法正确创建对象?

时间:2018-09-20 01:27:17

标签: typescript typescript-typings

采用以下代码

type PersonKeys = "name" | "age"
type PersonR = Record<PersonKeys, string>

const test = (human: Partial<PersonR>): PersonR => {
    return {name: "", age: "", widen: true} // // fails perfect can't widen
}

const yolo = (k: PersonKeys) => {
    return test(CreatePair(k, new Date()));  // fails perfect, can't take anything but string
}

// Disgusting solution
const CreatePair = <T extends (number | string), A>(key: T, value: A): Record<T, A> => {
    return {[key]: value} as any;
} 

问题很简单,我希望返回PersonR的所有内容都不能扩大以获取非PersonR上的属性。

但是,我还希望任何需要PersonR的Partial只接受VALID的部分,这意味着{[K作为Keyof Person]:字符串},而不是{[K作为Keyof Person]:日期}

如果不使用CreatePair函数,这似乎是不可能的。

如果不使用CreatePair,能否使两个注释仍然失败? 为什么使用具有日期值的对象代替CreatePair不会导致其失败?

2 个答案:

答案 0 :(得分:0)

  

问题很简单,我希望返回PersonR的所有内容都不能扩大以获取非PersonR上的属性。

TypeScript具有结构性。您可以随时添加属性。例如您的示例widen可以很容易地分配:

type PersonKeys = "name" | "age"
type PersonR = Record<PersonKeys, string>

const test = {name: "", age: "", widen: true};
const fail: PersonR = test; // PASS

唯一出错的情况是直接赋值(这是return语句正在执行的操作)。

答案 1 :(得分:0)

根据我对您问题的理解,您想确保在调用test时,参数仅具有PersonR指定的键。所以这些都应该失败:

test({ [k]: new Date() }); // has a string indexer, because of the computed property  
let o = { name: "", age: "", widen: true };
test(o); // not a direct assigment of an object literal, so this is allowed

没有通用的编译器切换到全局不允许这种情况。有人可以辩称,原则上不可能有这样的切换,因为在两种情况下,参数的类型都是参数类型的结构子类型,因此应在OOP规则下允许这样做。从OOP角度来看,怪异的检查是多余的属性检查,因为在这种情况下,我们无法将结构子类型分配给其超类型(基本上,在这种情况下,我们说let base:BaseType = new DerivedType()是错误)。

在现实世界中所说的多余属性可能表示错误(因此,在编译器中实现了多余属性检查)。虽然我们不能全局禁止使用多余的属性,但可以使用条件类型禁止参数包含多余的属性。我们在函数中使用泛型类型参数以获取参数的实际类型,如果此参数类型具有多余的属性(或string索引器),则会向参数类型添加字符串文字类型,这将触发错误:

type PersonKeys = "name" | "age"
type PersonR = Record<PersonKeys, string>

type StrictPropertyCheck<T, TExpected, TError> = Exclude<keyof T, keyof TExpected> extends never ? {} : TError;
const test = <T extends Partial<PersonR>>(human: T & StrictPropertyCheck<T, PersonR, "No extra properties!">): PersonR => {
    return {name: "", age: "", widen: true} // // fails perfect can't widen
}

const yolo = (k: PersonKeys) => {

    test({ [k]: new Date() }); // //error  
    let o = { name: "", age: "", widen: true };
    test(o); // error
}