【译】React, TypeScript 中 defaultProps 的类型解决
原文链接: React, TypeScript and defaultProps dilemma
github 的地址 欢迎 star!
前言
现在大型的前端项目中是很需要注意可维护性,易读性的,因此选择使用“安全的” JavaScript — 即 Typescript 是非常有帮助的,你工作将会变得更简单,避免一些潜在的错误问题。Typescript 搭配 React 能轻松的创建你的应用,配置好 tsconfig.json,那么 Typescript 就会指导你写出健壮的代码。一切都是那么顺利,直到你遇到一个“大的”问题—处理组件中的 defaultProps
作者在 github 上发布了 rex-tils代码库,其中包含了这篇文章讨论的解决办法
这篇文章是基于 TypeScript 2.9 版本以及开启严格模式进行的。我将会演示这个问题以及组件中怎么解决它
一个 Button 组件
我们开始定义一个 Button 组件,遵循以下的 API 实现:
Button API:
- onClick (点击事件)
- color (定义按钮颜色)
type (按钮的类型有 ‘button’ 或者 ‘submit’) 我们将 color 和 type 定义为可选项,用 defaultProps 定义他们的默认值(组件的使用者可能不会添加这些参数)
import {MouseEvent, Component} from ‘react’;
import * as React from ‘react’;type Props = {
onClick(e: MouseEvent<HTMLElement>): void;
color?: 'blue' | 'green' | 'red';
type?: 'button' | 'submit';
}
class Button extends Component{ static defaultProps = {
color: 'blue',
type: 'button'
};
render() {
const {onClick: handleClick, color, type, children} = this.props;
return (
<button
type={
type}
style={
{color}}
onClick={handleClick}
>
{children}
</button>
);
}
}
export default Button;
复制代码
Button 组件实现
现在,当我们在其他组件中使用 Button 时,编辑代码就能获取到相关的提示(可选项,必传的 Props)
但是,Button 组件的 defaultProps 属性没有被检查,因为类型检查器不能从泛型类扩展定义的静态属性中推断它的类型。
具体的解释:
- 设置了任意类型为静态类型
static defaultProps
- 定义两次相同的东西(类型和实现)
默认的 props 没有类型检查
可以通过分离 Props 提取出 color 和 type 的属性,然后用类型交叉把默认值映射为可选值。后面这步通过 TS 标准库中 Partial 类型来快速实现。
然后显示地指定 defaultProps 的类型是 DefaultProps
,这样检查器就能检查 defaultProps。
组件的 defaultProps 属性类型检查的实现
我倾向于使用下面的方法,就是提取 defaultProps 和 initialState (如果组件有 state)来分离状态类型,这样做另外的好处——从实现里面可以获取类型的明确定义,减少了定义 props 的模板,只保留核心必选的功能。
接下来在组件里面加入一点复杂的逻辑。 需求是,不希望只使用 css 内嵌样式(这是反设计模式以及性能糟糕的),而是能基于 color 属性,生成具有一些预定义的 css 类。
定义了 resolveColorTheme
函数,接受 color 的参数,返回自定义的 className。
但是,像上面那样会有一个编译错误的!
TS Error:
Type 'undefined' is not assignable to type '"blue" | "green" | "red"'
复制代码
可选的 props 导致的编译错误
为什么?color 是可选的,编译启用的是严格模式,而联合类型扩展存在 undefined/void 类型,但是函数不接受 undefined。
怎么修复这个价值很高的问题呢?
TypeScript 2.9 中提供了 4 中方法修复它:
- 非空断言语句(Non-null assertion operator)
- 组件类型重置(Component type casting)
- 高阶组件定义 defaultProps
- Props getter 函数
1. 非空断言语句
这个方法是显而易见的,就是明确告诉类型检查器,这不会是 null 或者 undefined,通过!
操作符实现:
在 render 方法中使用非空断言语句
对于简单的用例(props 属性很少的,仅在 render 方法接受特定的 props 的用例)这样做没问题的,随着业务的增长,这样的方法会加剧你组件的混乱,不可读。你需要花费大量时间检查哪个 props 被定义为 defaultProps,占用了开发者大量时间,这样也容易导致错误
2. 组件类型重置
那么怎么解决上一种问题的局限性呢?我们通过创建一个匿名类,断言它的类型为组合类型,设置 defaultProps 单个的类型(以及只读类型限制),如下:
这能解决我们当前的问题,但感觉是怪异的。
在 TypeScript 2.8 版本介绍了一种高级类型- 条件类型(conditional types)
T extends U ? X : Y
// 表示 T 如果继承自 U,那么它的类型就是 X,否则就是 Y
复制代码
3. 高阶函数定义 defaultProps
定义一个工厂函数/高阶函数,用于 defaultProps 和 条件类型的 props 合并。
其中 withDefaultProps 就是一个高阶函数,使用它,你不用明确地使用 React 的接口定义 defaultProps,另外如果不需要检查 defaultProps,可以删除上面代码中 type DefaultProps
。 但是要注意,它不能用于泛型组件,像这样:
你在泛型组件上使用了高阶函数(withDefaultProps 函数),会导致它的泛型丢失,
4. props getter 函数
使用工厂/闭包模式来实现条件类型映射。
像使用了
withDefaultProps
函数一样,利用了类型映射结构,不过没有将 defaultProps 映射为可选的,因为在实现组件时它不是可选的。
createPropsGetter 函数创建了一个闭包,通过泛型参数存储/推断出 defaultProps 类型。然后该函数返回了带有 defaultProps 的 props,从 TS 运行时角度看,它返回的 props 和我们传递的是相同的,因此 React 标准的 API就能获取运行时 props 的获取/解析。 如下实现:
Button 组件的 defaultProps 类型检查以及 props 实现
更进一步的解释:
createPropsGetter 实现组件类型的流程
到此为止,最终解决方案涵盖了所有上面的问题:
- 不需要使用非空断言语句来避开类型检查
- 不需要将组件强制间接地转换为其他类型
- 不需要再次创建组件,从而不会再进程中丢失类型
- 泛型组件也能使用
- 易于推理,TypeScript 3.0 版本支持
如果有错误或者不严谨的地方,请务必给予指正,十分感谢!
参考
- TS 官网高级类型
- TS 一些工具泛型的使用及其实现
还没有评论,来说两句吧...