确保打字稿界面中存在可选属性

时间:2018-09-01 10:10:21

标签: typescript

我对接口的打字稿可选属性有疑问。 假设以下代码:

interface Test {
    prop1: string;
    prop2?: string;
}

function someFunction(data: {prop1: string, prop2: string}) {
    console.log(data.prop1 + ": " + data.prop2);
}

function otherFunction(data: Test) {
    if (data.prop2) {
        someFunction(data); // prop2 might be undefined!
    }
}

,并将严格模式设置为true。

打字稿给我以下错误:

Argument of type 'Test' is not assignable to parameter of type '{ prop1: string; prop2: string; }'.
    Property 'prop2' is optional in type 'Test' but required in type '{ prop1: string; prop2: string; }'.

问题是:为什么会这样?断言为什么打字稿不理解这一点?

首先,我想知道为什么? 但是,如果可能的话,一些不产生任何附加运行时代码或大量类型声明的变通办法会很好吗?

4 个答案:

答案 0 :(得分:3)

Typescript确实可以像您所使用的那样理解类型保护,问题在于它们只会影响字段的类型,而不会影响整个对象。因此,例如在严格的null检查下,我们将得到以下内容:

function stringNotUndefined(s: string) {}


function otherFunction(data: Test) {
    stringNotUndefined(data.prop2) // error
    if (data.prop2) {
        stringNotUndefined(data.prop2) //ok
        someFunction(data); // still error
    }
}

我们可以创建一个自定义类型防护,将选中的字段标记为未定义:

interface Test {
    prop1: string;
    prop2?: string;
}
function someFunction(data: { prop1: string, prop2: string }) {
    console.log(data.prop1 + ": " + data.prop2);
}

type MakeRequired<T,K extends keyof T> = Pick<T, Exclude<keyof T, K>> & {[P in K]-?:Exclude<T[P],undefined> }
function checkFields<T, K extends keyof T>(o: T | MakeRequired<T,K>, ...fields: K[]) : o is MakeRequired<T,K>{
    return fields.every(f => !!o[f]);
}

function otherFunction(data: Test) {
    if (checkFields(data, 'prop2')) {
        someFunction(data); // prop2 is now marked as mandatory, works 
    }
}

修改

对于这种简单的检查,上面的版本可能会有太多的开销。我们可以为一个字段创建一个简单得多的版本(并为更多字段使用&&)。此版本的开销要少得多,如果在热路径上,甚至可以内联。

interface Test {
    prop1?: string;
    prop2?: string;
}
function someFunction(data: { prop1: string, prop2: string }) {
    console.log(data.prop1 + ": " + data.prop2);
}

type MakeRequired<T,K extends keyof T> = Pick<T, Exclude<keyof T, K>> & {[P in K]-?:Exclude<T[P],undefined> }
function checkField<T, K extends keyof T>(o: T | MakeRequired<T,K>,field: K) : o is MakeRequired<T,K>{
    return !!o[field]
}

function otherFunction(data: Test) {
    if (checkField(data, 'prop2') && checkField(data, 'prop1')) {
        someFunction(data); // prop2 is now marked as mandatory, works 
    }
}

答案 1 :(得分:0)

Typescript是经过静态类型检查的,因此从Test{prop1:string,prop2:string}的类型转换必须在编译时有效。 if条件是在运行时评估的,因此它不能用于静态类型检查分析(至少不是很简单的方式)。

可能可以设想出丰富Typescript的方法,以便可以使用防护来允许您希望执行的类型ot转换,但这根本不是当前设计的工作方式(而且比乍一看来)。

要执行您想做的事情,您可以编写一个辅助函数,该函数接受类型为Test的参数并返回类型为{prop1:string,prop2:string}的参数,方法是将可选参数填充一些默认值(如果有) Test参数中没有一个。

顺便说一句,您可能想看看Setting default value for TypeScript object passed as argument

中的讨论

答案 2 :(得分:0)

如果您想要特定的类型保护而不是Titian Cernicova-Dragomir的一般解决方案,则可以编写一个简单的函数。

interface TestWithProp2 {
    prop1: string;
    prop2: string; // required
}

// Special return type
function isTestWithProp2(x: Test): x is TestWithProp2 {
    return x.prop2 !== undefined;
}

// Use
if (isTestWithProp2(data)) {
    someFunction(data);
}

来源:Typescript: User defined type guards

答案 3 :(得分:0)

Typescript中有a Required built in type可以完全满足您的需求:

/**
 * Make all properties in T required
 */
type Required<T> = {
    [P in keyof T]-?: T[P];
};

您现在可以为data is Required<Test>定义类型防护:

const hasRequiredProp2 = (data: Test): data is Required<Test> => {
  return data.hasOwnProperty('prop2');
};

您需要做的就是使用类型防护来使用此测试,如下所示:

function otherFunction(data: Test) {
    if (hasRequiredProp2(data)) {
        someFunction(data); // Narrowed to Required<Test>
    }
}