阅读 2505

React 记录 - 使用 TS 编写 React 项目(1)

引言:TypeScript是一种由微软开发的自由和开源的编程语言。它是JavaScript的一个严格超集,并添加了可选的静态类型和使用看起来像基于类的面向对象编程语法操作Prototype。因此,我们可以像编写强类型语言(java, dart)等编码方式来编写javascript 应用程序。

现在各类前端项目都对 ts 进行了支持,以下是 ts 在 react 中使用的时候 各类组件使用方法。记录并分享给大伙。

类组件(webComponents 组件)

可以以下方法去编写一个 tsx 的 webComponent 组件。 该组件,将可以在不使用 Prop-types 类型检测库的情况下,对我们传入组件的 Props 进行约束,并且在编译器 上可以极其友好、明确的提示我们编写的组件的所接受的类型和方法等。

type IPrps = {
  message: string;
};

type IState = {
  count: number; 
};

class App extends React.Component<IPrps, IState> {
  state: IState = {
    count: 0
  };
  
  render() {
    return (
      <div>
        {this.props.message} {this.state.count}
      </div>
    );
  }
}

复制代码

React 官网对于 Component 的类型定义如下。

    interface Component<P = {}, S = {}, SS = any> extends ComponentLifecycle<P, S, SS> { }
复制代码

通过定义文件我们可以这样去编写我们的组件:

Component 接受泛型类 P & S & SS, 接受到 P 后,会将组件推导为 Readonly<{ children?: ReactNode }> & Readonly<P> 交叉类型,并且对 props 类型进行只读保护,我们不必特意在 Props 中特意去声明 children,也不需要在添加 Readonly 标识,因为这些都是自动的。


函数式组件 (Function Components)

函数式组件也并不违反开闭原则,因此我们可以像编写一个函数去约束参数类型一样去使用函数式组件。 可以通过以下方式去编写一个 tsx 版本的函数式组件:

type IProps = {
    onPress: () => void;
    iconStyle?: any;
    url: string,
};

const TouchIcon: React.FC<IProps> = (props: IProps) => {
    return (
        <div onClick={props.onPress}>
            <p>{props.url}</p>
            <p>{props.iconStyle}</p>
        </div>
    );
};
复制代码

React.FC 为 React.FunctionComponent 的简写,在使用和类型定义上两者没有任何区别。

此外,React.FC 组件,会在编译阶段自动为其静态属性,比如 displayName, propTypes, defaultProps 等进行类型补全。

    React SFC 组件
(Stateless Function Components)

在 React 16.8 以前,我们无法在函数式组件中去定义组件内部的状态,此时我们可以认为一个组件是 stateless(无状态的) 的,但是由于 React 16.8react-native 在 0.59 release 版本后才支持 Hooks 以后推出 React Hooks。stateless function components 这个定义不再准确。因此,我们现在不必在声明 React.SFC 了。


hooks 组件

我们可以通过下面的方法去编写 tsx 版本的 hooks 代码:


export type RecordItem = {
    title: string;
    total: number;
    id: number;
    color: string;
};

export const recordList: RecordItem[] = [
    {
        id: 1,
        total: 1500,
        title: "总完成课时",
        color: "rgb(20,125,255)",
    }, {
        id: 2,
        total: 30,
        title: "总完成课时",
        color: "rgb(255,96,96)",
    }
];

export const RecordWidget: React.SFC = () => {

    const [list, setList] = React.useState<RecordItem[]>(recordList);

    return ( 
        <>
            {
                list.map((item: RecordItem) => {
                    return (
                        <View style={{ backgroundColor: item.color }}>
                            <Text>{item.title}</Text>
                            <Text>{item.total}</Text>
                            <Text>{item.title}</Text>
                        </View>
                    );
                })
            }
        </>
    );
};

复制代码

如果需要使用默认值, 可以通过
const [list, setList] = React.useState<RecordItem[] | [] | null>(recordList); 来设置异常默认。

useRef

    const ref = useRef<HTMLElement | null>(null);
复制代码

useReducer

type AuthState = {};
type Action =
  | { type: "FETCH_SUCCESS"; payload: any }
  | { type: "FETCH_ERROR"; payload: string };

export function reducer(state: AuthState, action: Action): AuthState {
  switch (action.type) {
    case "FETCH_SUCCESS":
      return {
        ...state,
        one: action.payload
      };
    case "FETCH_ERROR":
      return {
        ...state,
        two: action.payload
      };
    default:
      return state;
  }
}
复制代码

事件处理

在定义 React 各类事件时候,使用 ts 我们可以根据业务场景去定义不同的事件类型来对代码进行静态检查。

比如某个表单提交事件:

<form
  ref={formRef}
  onSubmit={(e: React.SyntheticEvent) => {
    e.preventDefault();
  }}
</form>
复制代码

如此便可以拿到所有改事件类型下的方法和属性了。

根据业务我们需要选择不同的事件类型处理不同的业务场景,下面是几个常见事件的列举。

事件描述 事件类型 泛型类型
合成 事件 SyntheticEvent null
Change 事件 ChangeEvent <T = Element>
剪贴板事件 ClipboardEvent <T = Element>
拖拽事件 DragEvent <T = Element>
键盘事件 KeyboardEvent <T = Element>
鼠标事件 MouseEvent <T = Element>
触摸事件 TouchEvent <T = Element>
滚轮事件 WheelEvent <T = Element>
动画事件 AnimationEvent <T = Element>
过渡事件 TransitionEvent <T = Element>

Tips: React SyntheticEvent(合成事件)对象会被重用,为了性能原因,如果需要异步访问该事件对象,可以通过调用 event.persist() 来获取事件对象。


Redux

关于 action 的 ts 版本的推导和类型保护,有很多种方法和各种泛型版本。下面分享的是我在项目使用的方式供大家参考。

redux action 被设计为描述事件动作,并可以承接动作数据的载体(payload),下面是 redux 定义的 action 类型

在实际业务场景中,我们有时需要对 payload 载体进行约束,可以通过以下方式:

import { Action as ReduxAction } from 'redux';

export interface Action<T extends string, P = any> extends ReduxAction<T> {
    payload?: P;
}
复制代码

自定义 接口(interface)action 继承自 ReduxAction,拓展 payload 属性。使用方法如下:

此时,我们的 action 确定了入参以及返回的类型。由于 payload 是可选属性,因此在没有 payload 载体传入时,只需要这样定义即可:

上面的方法只是我们对 action 的类型进行了定义,当我们业务有多少个请求和交互要发出,可能就会有多个 Action。你也可以定义一个函数来生成 Action,这个函数就叫 Action Creator。

关于 action Creator 可以使用函数的重载来实现对象 action 和 payload 的支持,Action Creator 定义如下:

export function createAction<T extends string>(type: T): Action<T, void>;
export function createAction<T extends string, P>(
    type: T,
    payload: P
): Action<T, P>;
export function createAction<T extends string, P>(type: T, payload?: P) {
    return typeof payload === 'undefined' ? { type } : { type, payload };
}
复制代码

通过函数的重载,我们可以定义工厂方法来生产 action,并自动完成对 action 动作的校验。

上面是对于 redux action 动作的校验及推导的二种方式,有更多简单的方法,以上仅供参考

获取所有的 action 种类

既然对 action 进行约束,我们需要在代码上获取到当前所有的 action 种类。通常我们会在 store 仓库中新建 action 目录,在里面存放所有的 action 并在 index 中进行导出。像这样:

因为我们约定将所有的 action 定义在 action/ 文件夹下,因此我们可以这样去获取所有的 action 种类:

import * as actions from "@action/index";

type AllActions = keyof typeof actions[keyof typeof actions];
复制代码

获取所有的 redux state 类型

只需定义 类型 去取出所有的 state 种类就好了,像这样:

export type AllState =
    keyof AuthState | keyof CommonState |
    keyof ComFetchState | keyof ComNetworkState |
    keyof OperationState;
复制代码


感谢您的阅读~