用 typescript 写 react component 的
分享一篇讲 react ts 中模式的文章, 写的非常好
原文地址: https://levelup.gitconnected.com/ultimate-react-component-patterns-with-typescript-2-8-82990c516935
如果对下面代码不理解, 建议看下 https://basarat.gitbooks.io/typescript/content/docs/getting-started.html typescript deep dive 小白书
Stateless Component
按下面方式写 stateless component , 这样就不会报错了
import React, { MouseEvent, ReactNode } from 'react'
type Props = {
onClick(e: MouseEvent<HTMLElement>): void
children?: ReactNode
}
const Button = ({ onClick, children }: Props) => (
<button onClick={onClick}>{children}</button>
)
但是我们可以做的更好,在 @types/react
预先定义了 type SFC<P>
,它是 StatelessComponent<P>
的别名,它预先定义了 children 和一些其他的东西,譬如 defaultProps, displayName , 这样你就不用每次自己写,最后如下:
import React, { MouseEvent, SFC } from 'react'
type Props = {
onClick(e: MouseEvent<HTMLElement>): void
}
const Button: SFC<Props> = ({ onClick, children }) => (
<button onClick={handleClick}>{children}</button>
)
Stateful Component
让我们写一个计时器的 stateful component
首先我们定义下 initialState
const initialState = { clicksCount: 0 }
这样作我们不用分别管理 types 和 implementation(实现), 我们只有一个源, 那就是 implementation
type State = Readonly<typeof initialState>
同样注意 type 被明确 mapped 使得所有属性都是只读. 我们同样需要在我们的 class 上把 state 属性定义为 State type 类型, 并且设置成只读
readonly state: State = initialState
为什么要这样做? 我们知道我们不能像下面的方式直接更改 React 的 state
this.state.clicksCount = 2
this.state = { clicksCount: 2 }
这会在运行时抛出错误, 但是不是在编译的时候. 通过遍历 State type 申明的属性, 并设置成 readonly, 并且把组件的 state 也设置成 only, TS 在我们在我们写代码的时候就会立马知道
如下所示

整个组件实现:
我们的 Container 没有任何的 Props , 所以我们必须传入 object
做为第一个泛型参数, 使用 State
type 作为第二个泛型参数

你可能以及注意到, 我把 update state 的方法抽到 class 外面了. 这是一个经常用到的模式, 这使得我们可以很容易的测试, 而不需要关心 render 层逻辑. 并且因为我们使用 typescript, 并且 map State 属性成只读, 这同样能防止我们在这些方法里面对 state 做任何更改
const decrementClicksCount = (prevState: State)
=> ({ clicksCount: prevState.clicksCount-- })
// Will throw following complile error:
//
// [ts]
// Cannot assign to 'clicksCount' because it is a constant or a read-only property.
这看上去不错吧.
Default Props
让我们扩展我们的 Button component, 添加一个 color prop , 它是一个 string 类型
type Props = {
onClick(e: MouseEvent<HTMLElement>): void
color: string
}
如果我们想定义 defaultProps, 我们可以通过 Button.defaultProps = {...}
设定
By doing that we need to change our Props type definition to mark props that are default as optional.
So something like this ( notice the ? operator )
然而定义了默认属性, 我们必须更改 Props type, 因为已经设定了默认属性的可以不用传了, 变成了可选
就像下面那样, 注意 ?
号
type Props = {
onClick(e: MouseEvent<HTMLElement>): void
color?: string
}
然后我们的组件变成了这个样子:
const Button: SFC<Props> = ({ onClick: handleClick, color, children }) => (
<button style={{ color }} onClick={handleClick}>
{children}
</button>
)
这个简单的示例看上去没有问题, 但是实际这里面有个坑. 因为我们使用了严格模式, 我们的 color 设置成 optional prop, 它变成了 undefined | string
的联合类型.
但假如我们想对这种 optional props 做一些操作, TS 会抛出一个错误, 因为它不知道这个属性预先定义在了 defaultProps 上

为了使得 TS 不报错, 我们这里有三个做法:
- 使用 ! 操作符, 明确告诉 compiler 这个属性不是 undefined , 尽管这个属性是 optional. 像这样: <button onClick={handleClick!}>{children}</button>
- 使用条件判断操作符, 使编辑器明白这种特殊的 props 不会为 undefined: <button onClick={handleClick ? handleClick: undefined}>{children}</button>
- 写一个可复用的 withDefaultProps 高阶组件, 这个高阶组件会更新我们 props type 的定义, 并且会设定我们默认的 props. 这是最明确的解决方案, 在我看来
我们可以很容易实现我们的高阶函数(得益于 TS2.8 内置定义的 mapped types):

现在我们可以使用 withDefaultProps
高阶组件去定义我们的默认 props了, 这也同样能解决之前的问题,

或者直接写成内联 (注意, 我们不需要)

现在 Button Props 被定义成正确的类型了, defaultProps 体现出来了