原文:
Notes on TypeScript: React and Generics作者:A. Sharif
译者:博轩
介绍
这些笔记应该有助于更好的理解 TypeScript
,并且在查找如何在特定情况下使用 TypeScript
会很有帮助。所有示例都是基于 TypeScript 3.2
完成。
泛型
如果您一直在阅读“TypeScript 笔记”系列,那么到目前为止您将看到泛型的广泛使用。虽然我们一直在使用泛型,但是我们并没有真正的介绍泛型,以及它有什么用。在本系列的这一部分中,我们将首先尝试更好的理解泛型的概念,然后了解如何将泛型和 React 和 TypeScript 结合在一起更好的工作。
编写软件时,一方面是我们希望某些功能可以重用,而无需为每种可能的输入类型编写特定的功能。我们以下面这个例子作为起点:
function isDefinedNumber(a: number) : boolean {
return a !== null || a !== undefined;
}
function isDefinedString(a: string) : boolean {
return a!== null || a !== undefined;
}
我们不会编写显式的函数,使用 string
和 number
作为输入,而是编写一个带有一下签名的函数:
function isDefined<Type>(a: Type) : boolean {
return a!== null || a !== undefined;
}
isDefined
期望输入泛型 Type
。TypeScript 将尝试推断参数,并指定一个正确的类型。让我们接着看看下一个例子,推断一下返回的类型:
function of<Type>(a: Type) : Type[] {
return [a];
}
const toNumbers = of(1); // const toNumbers: number[]
const toStrings = of("Test Of"); // const toString: string[]
在 of
示例中,我们可以看到我们甚至不需要定义类型,因为 TypeScript 可以推断出参数类型。这并非适用于所有情况,有时候,我们必须明确说明类型。我们也可以这样定义上面的函数:
const toNumbers = of<number>(1); // const toNumbers: number[]
const toStrings = of<string>("Test Of"); // const toString: string[]
从技术上讲,我们可以使用 any
:
function of(a: any) : any {
if (a.length !== undefined) {
return a
}
return a;
}
但是使用any
和泛型之间会存在很大的差异。如果你仔细看看上面的例子,我们对输入的参数一无所知。of
使用 undefined
或者 null
值调用将导致错误。泛型可以推断出确切的类型,并强制在函数体内相应地处理输入。下面是使用泛型的相同示例:
function of<Type>(a: Type) : Type[] {
if (a.length !== undefined) { // error: Property 'length' does not exist on 'Type'
return a
}
return [a];
}
在处理泛型时我们必须更加明确,可以将示例重写为以下内容:
function of<Type>(a: Type | Type[]) : Type[] {
if (Array.isArray(a)) {
return a
}
return [a];
}
const a = of(1); // const a: number[]
const b = of([1]); // const b: number[]
泛型也可以应用于重用功能,如参数 a
可以对应类型 Type
或类型数组 Type
。当 1
作为参数传入时,泛型Type
会绑定到 number
类型 ,当传入 [1]
时会发生同样的情况,Type
会被绑定到 number
。
到这里我们已经看到如何在函数中使用泛型,但同时,我们也可以将泛型与类一起使用,这使得当你在 React 中编写类组件可能会非常有趣。
class GenericClass<Type> {
of = (a: Type | Type[]): Type[] => {
if (Array.isArray(a)) {
return a;
}
return [a];
};
}
const genericClass = new GenericClass<number>();
const a = genericClass.of(1); // const a: number[]
const b = genericClass.of("1"); // error!
const c = genericClass.of([1]); // const c: number[]
到目前为止我们看到的示例应该有助于我们理解基础知识,我们将基于这些知识,将泛型与React组件一起使用。
React 与泛型
使用 React 时,我们可能有一个函数组件,我们需要推断参数类型。这个组件需要一个 number
或者 string
类型的参数,或者一个number
或者 string
数组类型的参数。
type RowProps<Type> = {
input: Type | Type[];
};
function Rows<Type>({input}: RowProps<Type>) {
if (Array.isArray(input)) {
return <div>{input.map((i, idx) => <div key={idx}>{i}</div>)}</div>
}
return <div>{input}</div>
}
// usage
<Rows input={[1]} />
<Rows input={1} />
<Rows input={true} /> // Also works!
这样没问题,但是它限制依然适用于任何值。我们可以传入 true , TypeScript 不会抱怨。我们需要严格限制 Type
,或者让 Type
继承 string
或者 number
类型。
function Rows<Type extends number | string>({input}: RowProps<Type>) {
if (Array.isArray(input)) {
return <div>{input.map((i, idx) => <div key={idx}>{i}</div>)}</div>
}
return <div>{input}</div>
}
<Rows input={[1]} />
<Rows input={1} />
<Rows input="1" />
<Rows input={["1"]} />
<Rows input={true} /> //Error!
我们可以确保现在只能传入预期类型的参数。值得注意的是,我们也可以为 Props
定义通用类型,如上例所示:
type RowProps<Type> = {
input: Type | Type[];
};
接下来,我们将构建一个更高级的示例,以了解为什么泛型可以帮助我们构建可重用的React组件。我们将构建一个期望两个不同输入的组件。基于这些输入,我们将计算第三个值,并根据原始输入计算出新值,为 render
函数提供 props
。
type RenderPropType<InputType, OtherInputType> = { c: number } & InputType &
OtherInputType;
type RowComponentPropTypes<InputType, OtherInputType> = {
input: InputType;
otherInput: OtherInputType;
render: (props: RenderPropType<InputType, OtherInputType>) => JSX.Element;
};
第一步是定义 RowComponentPropTypes
,我们用TypeScript推断参数的类型,并将 RenderPropType
与 render
函数的 props
进行绑定 。RenderPropType
融合了新类型 {c: number}
产生的交集,我们将一起计算,InputType
和 OtherInputType
。到目前为止,我们一直在大量使用泛型。
我们可能不知道输入的确切类型,因此我们的下一步是在组件限制输入的类型。
class RowComponent<
InputType extends { a: number },
OtherInputType extends { b: number }
> extends React.Component<RowComponentPropTypes<InputType, OtherInputType>> {
// implementation...
}
通过使用 InputType extends { a: number }
我们确保我们的输入具有提供number
类型的a
属性。这个规则同样适用于 OtherInputType
。现在,我们实现了 RowComponent
并确保我们为 render
函数提供了 a
,b
,c
三个属性。
最后,这是我们完整的示例:
class RowComponent<
InputType extends { a: number },
OtherInputType extends { b: number }
> extends React.Component<RowComponentPropTypes<InputType, OtherInputType>> {
convert = (input: InputType, output: OtherInputType) => {
return { c: input.a + output.b, ...input, ...output };
};
render() {
return this.props.render(
this.convert(this.props.input, this.props.otherInput)
);
}
}
<RowComponent
input={{ a: 1 }}
otherInput={{ b: 2 }}
render={({ a, b, c }) => (
<div>
{a} {b} {c}
</div>
)}
/>
现在,我们对泛型,以及如何将 React
与 TypeScript
配合使用,应该有个大致的了解了。
本文已经联系原文作者,并授权翻译,转载请保留原文链接