TypeScript + React最佳实践-第一节:Component类型化

6,686 阅读2分钟

前言

TypeScript + React 类型安全三件套:Component、Redux、和Service类型化。

Component类型化

首先安装React类型依赖:

    // React源码改为TypeScript之前都要手动安装这些类型依赖
    npm i -D @types/react @types/react-dom

基础类型

组件泛型

React.ComponentType<P> = React.ComponentClass<P> | React.FunctionComponent<P>

只有组件类型【html标签字符串除外】可以创建 JSX.Element,示例:

    // 正确
    const C = () => <></>;
    const thisIsJSX = <C />;
    
    // 报错
    const D = () => [<></>];
    const throwError = <D />;

因此当我们需要通过props传递一个组件的时候,需要定义:

    interface Props {
        /** 自定义渲染组件 */
        Cp: React.ComponentType<any>;
    }

进而才能在接收该属性的组件里:

    props => {
        const { Cp } = props;
        return <Cp />;
    }

函数式组件泛型

React.FunctionComponent<Props, Context> 或者 React.StatelessComponent<Props, Context>, 可简写为 React.FC 或者React.SFC。React Hooks出现之后,React.StatelessComponentReact.SFC 被标记为“不建议使用”。

对应返回值必须是 JSX.Element,示例:

    // 以下是函数式组件
    const ThisIsFC = () => <></>;
    function ThisIsFCToo() {
        return <></>;
    }
    
    // 以下不是函数式组件
    const ThisNotFC = () => [<></>];
    function ThisIsNotFCNeither() {
        return [<></>];
    }

类组件泛型

React.ComponentClass<Props, State, Context>, 继承 React.Component 或者 React.PureComponent,示例:

    const C: React.ComponentClass = xxxxx;
    const jsx = <C />;

元素泛型

对应的是 React.ElementType<P>,等价的 React.ReactType 已被标记为“不建议使用”。

JSX.Element = React.ElementType<any>

组件类型化

props类型化

以函数式组件为例,定义:

    type Props = xxxxxxx;
    const ThisIsFC: React.FC<Props> = props => <></>;

其中 Props 可以分成以下即个子集,分别是:

  • OwnProps,即创建 JSX <ThisIsFC ... /> 直接传递的属性
  • StateProps,即通过 connect 到 redux store 获取的属性
  • DispatchProps,也是通过 connect 到 redux 获取的属性
  • RouteProps,即通过 react-router-dom Route 路由传递的属性

所以:

    Props = OwnProps & RouteProps & StateProps & DispatchProps;

定义 OwnPropsRouteProps 类型:

    interface OwnProps {
        /** name */
        name: string;
    }
    
    import { RouteComponentProps } from 'react-router-dom';
    type RouteProps = RouteComponentProps<{ id: string }>;

如此,我们可以在组件内部:

    const ThisIsFC = props => {
        {
            const { id } = props.match.params;
            // 正确
            cosnt str: string = id;
            // 报错
            const num: number = id;
        }
        {
            const { name } = props;
            // 正确
            cosnt str: string = id;
            // 报错
            const num: number = id;
        }
        return <></>;
    }

类型推断 StateProps & DispatchProps

StateProps & DispatchProps 是不能手写的,因为完全可以通过 map* 函数推断出来。

推断StateProps

type StateProps = ReturnType<typeof mapStateToProps>;

// TODO: Redux 类型化
function mapStateToProps(state: IRootState) {
    return {
        user: state.xxx.xxx
    };
}

推断DispatchProps

import { bindActionCreators, Dispatch } from 'redux';
// TODO: Redux 类型化
import actions from '../redux/actions';

type DispatchProps = ReturnType<typeof mapDispatchToProps>;

function mapDispatchToProps(dispatch: Dispatch) {
    return {
        actions: bindActionCreators(actions, dispatch)
    };
}

如此,我们可以在组件内部:

    const ThisIsFC = props => {
        props.user;
        props.actions.doSomething;
        return <></>;
    }

导出组件

import { connect } from 'react-redux';

export default connect<StateProps, DispatchProps, OwnProps>(
  mapStateToProps,
  mapDispatchToProps
)(ThisIsFC);

以上同样适用于类组件,只是类组件会多出一个 State 类型设置。

进阶

定义泛型组件

function ThisIsGenericFC<P extends string | number>(props: Props<P>) {}
class ThisIsGenericComponent<P extends string | number> extends React.Component<Props<P>, State> {}

const jsxFC = <ThisIsGenericFC<string> />;
const jsxComponent = <ThisIsGenericComponent<number> />;

预告

下一节“Redux 类型化”。