使用 TS 編寫 React 項目(1)

引言:TypeScript是一種由微軟開發的自由和開源的編程語言。它是JavaScript的一個嚴格超集,並添加了可選的靜態類型和使用看起來像基於類的面向對象編程語法操做Prototype。所以,咱們能夠像編寫強類型語言(java, dart)等編碼方式來編寫javascript 應用程序。javascript

如今各種前端項目都對 ts 進行了支持,如下是 ts 在 react 中使用的時候 各種組件使用方法。記錄並分享給大夥。前端

類組件(webComponents 組件)

能夠如下方法去編寫一個 tsx 的 webComponent 組件。 該組件,將能夠在不使用 Prop-types 類型檢測庫的狀況下,對咱們傳入組件的 Props 進行約束,而且在編譯器 上能夠極其友好、明確的提示咱們編寫的組件的所接受的類型和方法等。java

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 的類型定義以下。react

interface Component<P = {}, S = {}, SS = any> extends ComponentLifecycle<P, S, SS> { }
複製代碼

經過定義文件咱們能夠這樣去編寫咱們的組件:web

Component 接受泛型類 P & S & SS, 接受到 P 後,會將組件推導爲 Readonly<{ children?: ReactNode }> & Readonly<P> 交叉類型,而且對 props 類型進行只讀保護,咱們沒必要特地在 Props 中特地去聲明 children,也不須要在添加 Readonly 標識,由於這些都是自動的。編程


函數式組件 (Function Components)

函數式組件也並不違反開閉原則,所以咱們能夠像編寫一個函數去約束參數類型同樣去使用函數式組件。 能夠經過如下方式去編寫一個 tsx 版本的函數式組件:redux

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-native

此外,React.FC 組件,會在編譯階段自動爲其靜態屬性,好比 displayName, propTypes, defaultProps 等進行類型補全。bash

    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;
複製代碼


上面分享的都是一些簡單的 React 在結合 ts 使用時的打開方式。有些複雜的關於 hoc 或者是更詳細的業務場景的推導,能夠添加下面的微信羣組。你們一塊兒討論(不開車,只談論技術哈)。

相關文章
相關標籤/搜索