根据具有可选属性的类型从对象中提取实际类型



假设有几个可选字段的灵活类型:

type PushTypes = "start" | "finish" 
type PullTypes = "start" | "stop"
interface Notes {
push: Partial<Record<PushTypes, boolean>>;
pull?: Partial<Record<PullTypes, boolean>>;
}

和在这些类型中创建的具体对象

const DefaultServerNotes: Notes = {
push: {
start: true,
finish: true,
},
};
const DefaultClientNotes: Notes = {
push: { finish: true },
pull: { stop: true },
};

我如何从"具体"对象中提取类型,以便我最终得到以下类型?

// type ServerNotes = typeof DefaultServerNotes (nope)
type ServerNotes = {
push: {
start: boolean;
finish: boolean;
}
};
// type ClientNotes = typeof DefaultClientNotes 
type ClientNotes = {
push: { finish: boolean },
pull: { stop: boolean }
}

我已经梳理了常见嫌疑的语料库,但一无所获。我怀疑我的查询没有达到标准,使我没有一个满意的解决方案。

如果您将变量注释为您定义的Notes类型:

const DefaultServerNotes: Notes = {...}; // too late!

则不能从具体对象中提取类型。因为Notes不是联合类型(下面将详细介绍),所以Notes类型的值是并且将始终是Notes类型的值。编译器拥有的关于赋给变量的值的任何额外信息都被丢弃了,而要恢复这些信息已经太晚了。它的类型已经扩大Notes,而您希望类型保持


一种简单的方法是只用而不是注释这些变量:

const DefaultServerNotes = {
push: {
start: true,
finish: true,
},
};
type ServerNotes = typeof DefaultServerNotes
/* type ServerNotes = {
push: {
start: boolean;
finish: boolean;
};
} */

因为TypeScript的类型系统是结构化的,编译器会很乐意接受那些没有注释的变量,只要需要Notes值:

function needNotes(notes: Notes) {
console.log(notes.pull?.start);
}
needNotes(DefaultClientNotes); // okay
needNotes(DefaultServerNotes); // okay

和变量定义中的任何错误都将在那里被捕获:

const badNotes = { push: { stop: true } }
needNotes(badNotes); // error!
// Types of property 'push' are incompatible.

如果您确实希望错误出现在您分配变量的地方,您可以使用辅助函数而不是注释:

const asNotes = <T extends Notes>(n: T) => n;

asNotes()函数只接受可赋值给Notes的输入:

const oops = asNotes({
push: {
stop: true // error!
}
})

但不将其类型加宽为Notes:

const okay = asNotes({
push: {
start: true,
finish: true,
},
});
type Okay = typeof okay
/* type Okay = {
push: {
start: true;
finish: true;
};
} */
needNotes(okay); // okay

现在,如果Notes是联合类型,编译器将能够使用控制流分析将变量的表面类型缩小到变量赋值时的联合的某个子集。

不幸的是,您必须使Notes成为类型别名而不是接口,并拼写或计算您希望能够提取的每种可能的类型。我将只使用您明确提到的两个子类型,而不是一般地尝试这样做:

type ServerNotes = {
push: {
start: boolean;
finish: boolean;
}
};
type ClientNotes = {
push: { finish: boolean },
pull: { stop: boolean }
}
type Notes = ServerNotes | ClientNotes;

一旦你这样做了,收缩就会免费发生:

const DefaultServerNotes: Notes = {
push: {
start: true,
finish: true,
},
};
type SN = typeof DefaultServerNotes;
/* type SN = {
push: {
start: boolean;
finish: boolean;
};
} */

但我可能不会在这里推荐这种方法。

Playground链接到代码

最新更新