我们在TypeScript中构建了一个管道来处理MongoDB集合中的数据。
集合中的每个文档都有一个我们用来区分的type
属性。然后,我们通过处理器发送每个文档。
下面是我们代码的人为/简化版本:
type Processor = (input: BaseInput) => BaseOutput;
// base
interface BaseInput {
name: string;
}
interface BaseOutput {
id: number;
}
// one
interface InputOne extends BaseInput {
title: string;
}
interface OutputOne extends BaseOutput {
fullname: string;
}
// two
interface InputTwo extends BaseInput {
age: number;
}
interface OutputTwo extends BaseOutput {
parent: string;
}
const mapping = new Map<string, Processor>([
['one', processorOne],
['two', processorTwo],
]);
function processorOne(input: InputOne): OutputOne {
return {
id: 1,
fullname: input.title + input.name,
}
}
function processorTwo(input: InputTwo): OutputTwo {
return {
id: 2,
parent: input.name,
}
}
这在2.5
中工作正常,但我们在mapping
的strictFunctionTypes
2.6
中出现错误:
Argument of type '([string, (input: InputOne) => OutputOne] | [string, (input: InputTwo) => OutputTwo])[]' is not assignable to parameter of type '[string, Processor][]'.
Type '[string, (input: InputOne) => OutputOne] | [string, (input: InputTwo) => OutputTwo]' is not assignable to type '[string, Processor]'.
Type '[string, (input: InputOne) => OutputOne]' is not assignable to type '[string, Processor]'.
Type '(input: InputOne) => OutputOne' is not assignable to type 'Processor'.
Types of parameters 'input' and 'input' are incompatible.
Type 'BaseInput' is not assignable to type 'InputOne'.
Property 'title' is missing in type 'BaseInput'.
我理解为什么它会警告我,但是我们如何调整声明(或实现)以使其类型安全?
关于类型断言的其他答案很好,如果你愿意告诉编译器不要担心参数逆变。 但是,如果您希望编译器实际帮助您以类型安全的方式使用这些内容,则需要显式定义mapping
的键以及它与相关Processor
的确切输入和输出类型的关系。 来吧:
首先,让我们Processor
通用,以便您可以准确表达您正在使用的Processor
类型:
type Processor<I extends BaseInput=BaseInput, O extends BaseOutput=BaseOutput>
= (input: I) => O;
因此,普通Processor
与以前相同,但现在您可以专门讨论Processor<InputOne, OutputOne>
作为处理器,它将InputOne
作为输入并产生和OutputOne
作为输出。
现在让我们描述一下您要存储在mapping
中的事物的类型:
interface ProcessorTypeMappings {
one: { input: InputOne, output: OutputOne };
two: { input: InputTwo, output: OutputTwo };
}
这有点冗长(稍后会导致更冗长),但如果您需要查询,例如,仅查询mapping.get('two')
的输入类型,稍后会有所帮助。 在任何情况下,您都可以为更多处理器添加上述属性。
我们可以使用ProcessorTypeMappings
更准确地描述mapping
的功能。 我只打算指定get()
和set()
...这并不完美,但您可以根据需要进行调整:
interface ProcessorMap extends Map<string, Processor<any, any>> {
get<K extends keyof ProcessorTypeMappings>(key: K):
Processor<ProcessorTypeMappings[K]['input'], ProcessorTypeMappings[K]['output']>;
set<K extends keyof ProcessorTypeMappings>(key: K,
val: Processor<ProcessorTypeMappings[K]['input'], ProcessorTypeMappings[K]['output']>): this;
}
所以get()
从ProcessorTypeMappings
中获取一个键,并产生一个值,这是一个Processor<I,O>
,其中I
是ProcessorTypeMappings
中相关属性的input
属性,O
是对应的output
属性。set()
是相似的,只是它同时需要一个键和一个值。
现在,最后,您可以将mapping
创建为ProcessorMap
并填充它:
const mapping: ProcessorMap = new Map<string, any>();
mapping.set('one', processorOne); // okay
mapping.set('two', processorTwo); // okay
那些类型检查很好。 我使用set()
而不是使用构造函数参数来做到这一点,这样做会更烦人(因为构造函数是它自己的类型)。
而且,当您稍后使用mapping
时,您将获得类型安全:
declare const inputOne: InputOne;
declare const inputTwo: InputTwo;
const outputOne = mapping.get('one')(inputOne); // OutputOne
const oops = mapping.get('three'); // no such key
const nope = mapping.get('two')(inputOne); // InputOne not assignable to InputTwo
游乐场链接
希望有帮助;祝你好运!
更新
我突然想到(回想起来这很明显)如果你真的只有从字符串值键到特定处理器类型的简单映射,那么你可能想使用一个普通的旧常规对象而不是任何类型的Map
。 字符串值键和不同类型的值是对象最擅长的。 TypeScript 将帮助推断出这样一个对象的相当特定的类型,所以你甚至不需要声明一堆接口或类型:
这是您的mapping
:
const mapping = {
one: processorOne,
two: processorTwo
};
就是这样。 并对其进行测试:
declare const inputOne: InputOne;
declare const inputTwo: InputTwo;
const outputOne = mapping.one(inputOne); // OutputOne
const oops = mapping.three; // no such key
const nope = mapping.two(inputOne); // InputOne not assignable to InputTwo
简单得离谱。 您可能会认为您的用例足够复杂,需要上述更重的机器;这取决于你。 但越简单越好,所以我想确保呈现它。
好的,再次祝你好运!
编译器没有错,因为它无法证明processorOne
分配给Processor
是安全的。你说你"知道"基于你需要传递给InputOne
的类型属性processorOne
即使Process
的参数是类型BaseInput
。
当我们"知道"编译器不知道的东西时,最简单的解决方案是使用类型断言:
const mapping = new Map<string, Processor>([
['one', <Processor>processorOne],
['two', <Processor>processorTwo],
]);
更安全的解决方案是使Processor
通用并使用知道要Processor
的通用参数的自定义映射。但是在这里,您基本上是将断言推送到映射中,并伪造您的使用者来指定不理想的类型参数。
class ProcessorMap {
private innerMap = new Map<string, any>() ;
set<TIn extends BaseInput, TOut extends BaseOutput>(key: string, processor: Processor<TIn, TOut>) {
this.innerMap.set(key, <any>processor);
return this;
}
get<TIn extends BaseInput, TOut extends BaseOutput>(key: string) {
return this.innerMap.get(key);
}
}
const mapping = new ProcessorMap()
.set('one', processorOne)
.set('two', processorTwo);