用 typescript 写 react component 的

2018-08-28  本文已影响8人  472abb2e4941

分享一篇讲 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 作为第二个泛型参数

image.png

你可能以及注意到, 我把 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 上

color 属性有 default 值, 但 ts 仍然认为它可能是 undefined

为了使得 TS 不报错, 我们这里有三个做法:

我们可以很容易实现我们的高阶函数(得益于 TS2.8 内置定义的 mapped types):

image.png

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

image.png

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

image.png

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

上一篇下一篇

猜你喜欢

热点阅读