TypeScript在React高阶组件中的使用技巧

6,265 阅读3分钟

组件参数的编译期检查

随着TypeScript语言特性的完善,我们团队的前端代码已完全迁移至TypeScript。当前TypeScript完全支持JSX语法,举个例子:

import * as React from 'react'

type Props = {
  p1: string
}

class ComponentA extends React.Component<Props> {
  render(){
    return <div>{this.props.p1}</div>
  }
}

<ComponentA /> //编译时错误,p1必须传入
<ComponentA p1="test"/> //正确

这样我们声明的ComponentA组件就可以在编译时检查传入参数是否正确。

如果我们希望组件的某个参数是选填的,只需要在参数定义前加一个问号即可:

import * as React from 'react'

type Props = {
  p1?: string
}

class ComponentA extends React.Component<Props> {
  render(){
    return <div>{this.props.p1 || "empty"}</div>
  }
}

<ComponentA /> //正确,p1为非必需参数
<ComponentA p1="test"/> //正确

高阶组件

在使用React的过程中,我们不可避免的会对现有组件进行再封装,形成很多高阶组件。

对于这种情况,我们首先想到的方案是编写独立的高阶组件,如:

import * as React from 'react'

type BCProps = {
  p1: string
}

class BC extends React.Component<BCProps> {
  render(){
    return <div>{this.props.p1}</div>
  }
}

type HOCProps = {
  p1h:string
}

class HOC extends React.Component<HOCProps> {
  render(){
    return <BC p1={this.props.p1h}/>
  }
}

在上面的例子中,BC是基础组件,HOC是我们封装的高阶组件。我们可以看到BCProps与HOCProps是相互独立的,这样做的好处有以下两点:

  1. 适用性广,任何场景都可使用此种封装方式。
  2. 完全解耦,使用者只需要了解HOCProps,对BCProps无需关心。

但相对的,其缺点也非常明显,由于对BC进行了完全封装,当BCProps改变时,如p1更改了名称,改为了p1b,此时我们需要更改HOC的代码,否则运行会报错。

举一个很常见的例子,一个基础组件自带样式并不符合我们的需求,我们希望能封装一个自定义样式的高阶组件,而其他Props保持不变。这样的话,在样式参数的定义不变而其他参数变动的情况下,可以做到高阶组件的代码无需进行修改。

在JS中,实现这种参数传递非常的简单,但是在TS中会有些麻烦。最笨的方法是我们手动将BCProps中的样式无关参数复制一份,但是这样的话如果BCProps被修改,那么我们需要手动修改HOCProps,无法做到非样式参数改变,代码无需修改的目的。

在当前的TS版本中,我们无法直接对已存在类型的属性进行删除操作,如果要实现这种高阶组件,首先我们需要定义两个帮助类型:

type Diff<T extends string, U extends string> = ({[P in T]: P } & {[P in U]: never } & { [x: string]: never })[T];

type Omit<T, K extends keyof T> = {[P in Diff<keyof T, K>]: T[P]};

这样,我们就可以实现不含样式参数的高阶组件:

import * as React from 'react'

type BCProps = {
  p1: string,
  style: React.CSSProperties
}

class BC extends React.Component<BCProps> {
  render(){
    return <div style={this.props.style}>{this.props.p1}</div>
  }
}

type HOCProps = Omit<BCProps, "style">

class HOC extends React.Component<HOCProps> {
  render(){
    return <BC {...Object.assign({},this.props,{style:{color:"red"}})}/>
  }
}

只要style参数的使用方法不变,当BC被修改时,HOC无需任何改变。