Typescript 2.3 实现了泛型参数默认值,它允许你给泛型的参数类型设置默认类型。
在这篇文章中,我会通过将下面的 React 组件从 Javascript(即 JSX) 转成 Typescript(即 TSX) 来展开我们如何能够从泛型参数默认值中获益:
class Greeting extends React.Component {
render() {
return <span>Hello, {this.props.name}!</span>;
}
}不用担心,你不用懂 React 也能看懂下面的内容。
让我们先来给 Component 这个类创建一个类型定义。每一个 React 类组件都有两个属性 pops 和 state,它们的类型都是任意的。所以我们可以定义如下的类型:
declare namespace React {
class Component {
props: any;
state: any;
}
}注意为了说明的目的这个例子中做了极大的简化。毕竟,这篇文章不是在讲 React,而是关于泛型参数和它的默认值。真实的DefinitlyTyped 上的 React 类型定义 要复杂得多。
现在我们得到了类型检查和自动补全建议:
class Greeting extends React.Component {
render() {
return <span>Hello, {this.props.name}!</span>;
}
}我们可以像下面这样创建一个组件实例:
<Greeting name="World" />渲染我们的组件会生成下面的 HTML,和我们期望的一样:
<span>Hello, World!</span>目前来看一切都很好。
虽然上面的例子可以编译和运行正常,但 Componnent 的类型定义非常不准确。因为我们将 props 和 state 定义为 any 类型,Typescript 编译器并帮不了太多的忙。
让我们来把它们变得更具体一些。我们会引入两个泛型 Props 和 State 来准确地描述 props 和 state 两个属性的形状:
declare namespace React {
class Component<Props, State> {
props: Props;
state: State;
}
}我们再来创建一个 GreetingProps 类型,它定义了一个 string 类型的 name 属性,并且作为实参传给 Props 类型参数:
type GreetingProps = { name: string };
class Greeting extends React.Component<GreetingProps, any> {
render() {
return <span>Hello, {this.props.name}!</span>;
}
}术语解释:
GreetingProps 是 Props 参数的类型实参any 是 State 参数的类型实参有了这些类型,我们的组件会得到更好的类型检查和自动补全建议:

然而,如果我们想要继承 React.Componnet 类,我们现在必须提供两个类型。我们最初的代码不再是类型正确的:
// Error: Generic type 'Component<Props, State>'
// requires 2 type argument(s).
class Greeting extends React.Component {
render() {
return <span>Hello, {this.props.name}!</span>;
}
}如果我们不想提供类似 GreetingProps 这样具体的类型,我们可以通过提供 any 类型(或者其他不太有用的类型比如 {})给 Props 和 State 来修复代码:
class Greeting extends React.Component<any, any> {
render() {
return <span>Hello, {this.props.name}!</span>;
}
}这种方式可以正常工作并且通过类型检查,但是假如 any 在这个例子中是默认值,并且可以简单地不用传任何实参不是更好吗?好,那我们就来介绍泛型参数默认值。
从 Typescript 2.3 开始,我们可以选择性地给我们的泛型类型参数添加一个默认的类型。在我们的例子中,我们可以将 Props 和 State 的默认值设置为 any 类型,如果没有显式地设置类型实参,就会使用该默认值:
declare namespace React {
class Component<Props = any, State = any> {
props: Props;
state: State;
}
}通过这种方式,我们最初始的代码再一次通过了类型检查并且成功编译:
class Greeting extends React.Component {
render() {
return <span>Hello, {this.props.name}!</span>;
}
}当然,我们依然可以给 Props 类型参数提供类型实参,它会覆盖默认的 any 类型,就像我们之前做过的一样:
type GreetingProps = { name: string };
class Greeting extends React.Component<GreetingProps, any> {
render() {
return <span>Hello, {this.props.name}!</span>;
}
}我们还可以做些其他有趣的事。现在这两个参数都有了默认的类型,这让它们的实参变得可选——你可以不用提供实参。这允许我们给 Props 显式地设置一个类型,同时让 State 隐式地使用默认的 any 类型。
type GreetingProps = { name: string };
class Greeting extends React.Component<GreetingProps> {
render() {
return <span>Hello, {this.props.name}!</span>;
}
}注意我们只提供了一个类型实参。我们可以选择性地不给右边的参数传实参。也就是说,在这个例子中,我们不能给 State 设置类型实参同时让 Props 使用默认参数。类似地,在定义类型的时候,可选的类型参数不能放在必需参数的前面。
在我的上一篇文章 Typescript 中的 mixin 类 中,我一开始就定义了下面的两个类型别名:
type Constructor<T> = new (...args: any[]) => T;
type Constructable = Constructor<{}>;Constructable 类型是单纯的语法糖。它可以替代 Constructor<{}>,这样我们不用每次都写泛型类型参数。那有了泛型类型默认值,我们完全可以不用写 Constructable 类型,只需要将 {} 设为默认值即可:
type Constructor<T = {}> = new (...args: any[]) => T;这个语法相对复杂一些,但代码变得更简洁了。很好!