我有一个返回方法的工厂。工厂可以设置默认的id
,在这种情况下,不再需要将其传递给方法。
这是一个简化的测试用例(Playground)
type FactoryOptions = {
id?: number
}
type MethodOptions = {
id?: number
}
function factory (options: FactoryOptions) {
return method.bind(null, options)
}
function method (state: FactoryOptions, options: MethodOptions) {
const id = state.id || options.id
console.log(id.toString())
}
id.toString()
只是为了触发TypeScript抱怨id
在这一点上可能是未定义的。这个问题的上下文是octokit/auth-app.js#5,它更复杂。
答案 0 :(得分:4)
此问题有两个方面:method()
的调用方和method()
的实现。在TypeScript中,通常要使类型检查器对调用方表现良好,而不是使其在实现内部起作用。
从调用者的角度,我们可以确保必须使用包含已定义的method
属性的两个参数中的至少一个来调用id
。一种实现方法是使用overloads,由于这是我能想到的最不疯狂的解决方案,因此,我将向大家展示(其他解决方案包括使用tuple types as a rest parameter的并集):< / p>
type WithId = {
id: number;
};
function method(state: FactoryOptions & WithId, options: MethodOptions): void;
function method(state: FactoryOptions, options: MethodOptions & WithId): void;
function method(state: FactoryOptions, options: MethodOptions) {
// impl
}
您可以在此处调用第一个或第二个调用签名,并且实现签名对调用者隐藏:
method({}, {}) // error
method({id: 1}, {}) // okay
method({}, {id: 1}) // okay
在实现过程中,事情并不那么令人愉快。没有简单的方法可以使编译器确信类型[number, number | undefined] | [number | undefined, number]
的值在第一个或第二个元素处都有已定义的值。也就是说,编译器没有将这样的并集视为discriminated union,因此将第一个元素与undefined
进行检查对编译器视为第二个并集没有影响。您也许可以为此实现某种user-defined type guard,但这是过大的。
相反,我们只接受我们比编译器更聪明,并使用type assertion:
function method(state: FactoryOptions & WithId, options: MethodOptions): void;
function method(state: FactoryOptions, options: MethodOptions & WithId): void;
function method(state: FactoryOptions, options: MethodOptions) {
const id = (typeof state.id !== "undefined"
? state.id
: options.id) as number; // assert as number
console.log(id.toString()); // okay now
}
还要注意,我将您对state.id
的支票更改为使用三元,因为0
是虚假的,而0 || undefined
是undefined
。我假设您的意思是id
始终是一个数字,这由三元校验(避免了虚假性)保证。
那可能不是您希望的,但这是我能做的最好的。希望能有所帮助;祝你好运!
答案 1 :(得分:1)
除了通过jcalz
的出色答案之外,还有另一种(playground)供您考虑。
type FactoryOptions = {
id?: number
}
type MethodOptions = {
id?: number
}
function factory (options: FactoryOptions) {
return method.bind(null, options)
}
const hasId = (input: any): input is {id: number} =>
typeof input.id !== 'undefined';
function method (state: Omit<FactoryOptions, 'id'>, options: Required<MethodOptions>): void;
function method (state: Required<FactoryOptions>, options: Omit<MethodOptions, 'id'>): void;
function method (state: FactoryOptions, options: MethodOptions): void {
if(hasId(state)) {
console.log(state.id.toString());
} else if (hasId(options)) {
console.log(options.id.toString());
}
}
method({}, {}); // error
method({ id: 10 }, {}); // ok
method({ }, { id: 10 }); // ok