我们已经构建了一个管道,用于处理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
中工作正常,但我们2.6
strictFunctionTypes
mapping
的{{1}}收到错误:
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'.
我理解为什么它会警告我,但是我们如何才能将声明(或实现)调整为类型安全?
答案 0 :(得分:3)
关于类型断言的其他答案是好的,如果你很乐意告诉编译器不要担心参数的逆转。但是,如果您希望编译器实际上以类型安全的方式帮助您使用这些内容,则需要明确定义mapping
的键以及它与{{的确切输入和输出类型的关系。 1}}有问题。我们走了:
首先,让我们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
属性input
,ProcessorTypeMappings
是相应的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
。字符串值键和不同类型的值是对象最好的。而TypeScript将有助于推断出这种对象的相当具体的类型,因此您甚至不需要声明一堆乱码或接口:
这是您的Map
:
mapping
就是这样。并测试它:
const mapping = {
one: processorOne,
two: processorTwo
};
非常简单。你可能会认为你的用例很复杂,需要上面较重的机器;随你(由你决定。但更简单通常更好,所以我想确保提出它。
好的,祝你好运!
答案 1 :(得分:1)
The compiler is not wrong about the fact it can't prove that the assignment processorOne
to Processor
is safe. You say you "know" based on a type property that you need to pass InputOne
to processorOne
even though the argument to Process
is of type BaseInput
.
The simplest solution for when we "know" something the compiler does not know is to use a type assertion:
const mapping = new Map<string, Processor>([
['one', <Processor>processorOne],
['two', <Processor>processorTwo],
]);
The safer solution would be to make Processor
generic and use a custom map that knows about the generic arguments to Processor
. But here you are basically pushing the assertions to the map and forging your consumers to specify the type arguments which is not ideal.
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);