使用 TypeScript + React + Redux 進行專案開發(入門篇,附原始碼)

暖生發表於2019-03-08

本文詳細介紹瞭如何使用 Create-React-App 編寫 TypeScript + React 專案

前言

對於 TypeScript + React 開發,MicroSoft 編寫了一個 TypeScript-React-Starter 的例子,Github 地址。有需要的朋友可以去看一下。

我自己也看了一下,文件說明講解的很好,但是 Demo 拉下來卻無法正常執行,一直報錯。所以我自己使用 TypeScript + React + Redux 寫了 Demo,作為範例來用一下。

本文 Demo 地址

  • 本文 Counter Demo 是一個簡易的例子,可以用來作為入門參考,Counter Demo

  • 另外還寫了一個 TodoList 的例子,稍微更有難度一些,程式碼量和元件更多更詳細。有需要的朋友也可以參考一下。TodoList Demo

建議

可以先下載 Counter Demo 後,執行專案,檢視執行效果,然後對照本文進行閱讀,效果更佳!

使用 TypeScript 編寫 React 需要注意的規範

  • 必須遵守的要求:

    • 所有用到 jsx 語法的檔案都需要以 tsx 字尾命名
    • 使用元件宣告時的 Component<P, S> 泛型引數宣告,來代替 PropTypes進行型別校驗
  • 額外的程式碼規範:

    • 全域性變數或者自定義的 window 物件屬性,統一在專案根下的 global.d.ts 中進行宣告定義
    • 對於專案中常用到的介面資料物件,最好在 types/ 目錄下定義好其結構化型別宣告

安裝 Create-React-App

$ npm install create-react-app -g
複製程式碼

建立專案

先建立一個新的專案,這裡我們命名為 typescript-react-app

$ create-react-app typescript-react-app --scripts-version=react-scripts-ts
複製程式碼

react-scripts-ts是一系列介面卡,它利用標準的create-react-app工程管道並把TypeScript混入進來。

專案建立成功後,此時專案結構如下所示:

my-app/
├─ node_modules/
├─ public/
├─ src/
│  └─ ...
├─ .gitignore
├─ images.d.ts
├─ package.json
├─ README.md
├─ tsconfig.json
├─ tsconfig.prod.json
├─ tsconfig.test.json
├─ tslint.json
└─ yarn.lock
複製程式碼

注意:

  • tsconfig.json包含了工程裡TypeScript特定的選項。
  • tslint.json儲存了要使用的程式碼檢查器的設定,TSLint。
  • package.json包含了依賴,還有一些命令的快捷方式,如測試命令,預覽命令和釋出應用的命令。
  • public包含了靜態資源如HTML頁面或圖片。除了index.html檔案外,其它的檔案都可以刪除。
  • src包含了TypeScript和CSS原始碼。index.tsx是強制使用的入口檔案。

執行專案

先執行專案,看看是否能夠正常啟動,如果可以,說明專案建立沒有問題。 執行命令:

$ npm run start

# 或者執行 yarn run start
複製程式碼

React 配合 TypeScript 的基本使用

在當前專案中,可以看到 index.tsx 和 App.jsx 檔案中已經使用了 TypeScript,我們現在自己來用 TypeScript 編寫一個 React 元件吧。

定義一個 Counter 元件

我們在 src 下建立一個 components 目錄,新增 Counter 元件:

Counter.tsx

import * as React from 'react';


// 建立型別介面
export interface Iprops {
    value: number
}

// 使用介面代替 PropTypes 進行型別校驗
const Counter = ({ value }: Iprops) => {
    return <p>Clicked: { value } times</p>
}

export default Counter;
複製程式碼

在 App.tsx 中引用 Counter 元件並展示

import * as React from 'react';
import './App.css';

import Counter from './components/Counter.jsx';
// import logo from './logo.svg';

class App extends React.Component {
  public render() {
    return (
      <div className="App">
        <Counter value={ 0 } />
      </div>
    );
  }
}

export default App;
複製程式碼

執行專案:npm run start,可以看到瀏覽器中展示出了 Clicked: 0 times,說明我們第一個 Counter 元件已經編寫並使用成功了。

使用類的方式定義 Counter 元件

剛才是使用函式元件的方式定義的 Counter 元件,現在我們使用類的方式來改寫一下。兩種方式都試一試:

Counter.tsx

import * as React from 'react';


// 建立型別介面
export interface IProps {
    value: number
}

// 使用介面代替 PropTypes 進行型別校驗
export default class Counter extends React.PureComponent<IProps> {
    public render() {
        return <p>Clicked: { this.props.value } times</p>
    }
}
複製程式碼

進階:在專案中配合 Redux 進行使用

安裝專案需要的外掛

安裝redux和react-redux以及它們的型別檔案做為依賴。

$ npm install -S redux react-redux @types/react-redux
複製程式碼

這裡我們不需要安裝@types/redux,因為Redux已經自帶了宣告檔案(.d.ts檔案)。

定義應用的狀態 State

一般會將常用的結構型別存放到 /types 目錄下。所以我們在 src 目錄下新建 types 目錄。 此時專案中只有一個 state,就是 Counter 中的點選次數,所以就沒有使用藉口來作為約束,而是直接使用了 type。

type/index.tsx

// 定義 State 結構型別
export type StoreState = number;
複製程式碼

新增 actions

在 src 下建立 constants 目錄,在 index.tsx 檔案中新增需要響應的訊息型別

constants/index.tsx

// 定義增加 state 型別常量
export const INCREMENT = "INCREMENT";
export type INCREMENT = typeof INCREMENT;

// 定義減少 state 型別常量
export const DECREMENT = "DECREMENT";
export type DECREMENT = typeof DECREMENT;
複製程式碼

這裡的const/type模式允許我們以容易訪問和重構的方式使用TypeScript的字串字面量型別。 接下來,我們建立一些 actions 以及建立這些 actions 的函式,src/actions/index.tsx。

actions/index.tsx

export interface IINCREMENTAction {
    type: INCREMENT;
}

export interface IDECREMENTAction {
    type: DECREMENT;
}

// 定義 modifyAction 型別,包含 IINCREMENTAction 和 IDECREMENTAction 介面型別
export type ModifyAction = IINCREMENTAction | IDECREMENTAction;


// 增加 state 次數的方法
export const increment = (): IINCREMENTAction => ({
    type: INCREMENT,
})

// 減少 state 次數的方法
export const decrement = (): IDECREMENTAction => ({
    type: DECREMENT
})
複製程式碼

actions/index.tsx 中定義了兩個型別,分別負責新增和減少操作的行為。我們還定義了一個型別(ModifyAction),它描述了哪些 action 是可以增加或減少的。 最後,我們定義了兩個函式用來建立實際的 actions

新增 reducer

我們的reducer將放在src/reducers/index.tsx檔案裡。 它的功能是保證增加操作會讓 times 加1,減少操作則要將 times 減1。

reducers/index.tsx

import { ModifyAction } from '../actions';
import { DECREMENT, INCREMENT } from '../constants';


// 處理並返回 state 
export default (state = 0, action: ModifyAction): number => {
    switch (action.type) {
      case INCREMENT:
        return state + 1
      case DECREMENT:
        return state - 1
      default:
        return state
    }
}
複製程式碼

建立容器元件

之前我們已經使用了 Counter 元件,但是這個元件是一個純元件,此時我們需要一個元件將 Counter 和 資料連線起來。我們先修改一下原先的 Counter 元件,在其中新增一些操作按鈕

components/Counter.tsx

import * as React from 'react';


// 建立型別介面
export interface IProps {
    value: number,
    onIncrement: () => void,
    onDecrement: () => void
}

// 使用介面代替 PropTypes 進行型別校驗
export default class Counter extends React.PureComponent<IProps> {
    public render() {
        const { value, onIncrement, onDecrement } = this.props;
        return (
            <p>
                Clicked: { value } times
                <br />
                <br />
                <button onClick={ onIncrement } style={{ marginRight: 20 }}> +  </button>
                <button onClick={ onDecrement }> - </button>
            </p>
        )
    }
}

複製程式碼

然後我們再建立一個 container 目錄,用來存放需要與資料互動的元件,新建 CounterCon.tsx 檔案.

兩個關鍵點是初始的 Counter 元件和 react-reduxconnect 函式。 connect 可以將我們的 Counter 元件轉換成一個容器,通過以下兩個函式:

  • mapStateToProps將當前store裡的資料以我們的元件需要的形式傳遞到元件。
  • mapDispatchToProps利用dispatch函式,建立回撥props將actions送到store。

container/CounterCon.tsx

import { connect } from 'react-redux';
import { Dispatch } from 'redux';

import { decrement, increment } from '../actions';
import Counter from '../components/Counter';
import { StoreState } from '../types';


// 將 reducer 中的狀態插入到元件的 props 中
const mapStateToProps = (state: StoreState): { value: number } => ({
    value: state
})

// 將 對應action 插入到元件的 props 中
const mapDispatchToProps = (dispatch: Dispatch) => ({
    onDecrement: () => dispatch(decrement()),
    onIncrement: () => dispatch(increment())
})

// 使用 connect 高階元件對 Counter 進行包裹
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
複製程式碼

建立 store

讓我們回到src/index.tsx。 要把所有的東西合到一起,我們需要建立一個帶初始狀態的store,並用我們所有的reducers來設定它。 並且使用 react-redux 的 Provider 將 props 和 容器連線起來

index.tsx

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';

import App from './App';
import './index.css';
import reducer from './reducer'; 
import registerServiceWorker from './registerServiceWorker';


// 1、建立 store
const store = createStore(reducer);

ReactDOM.render(
    // 2、然後使用react-redux的Provider將props與容器連通起來
    <Provider store={ store }>
        <App />
    </Provider> ,
    document.getElementById('root') as HTMLElement
);
registerServiceWorker();
複製程式碼

回到我們的 App.jsx 檔案中,之前我們引用的是 components 中的 Counter 元件,但是此時我們需要使用的是與資料有互動的 CounterCon 元件。改寫如下:

App.jsx

import * as React from 'react';
import './App.css';

// 引入 container 元件 CountCon
import CountCon from './container/CountCon';
// import logo from './logo.svg';

class App extends React.Component {
  public render() {
    return (
      <div className="App">
        <CountCon />
      </div>
    );
  }
}

export default App;
複製程式碼

注意,此時 CountCon 不再需要 props 了,因為我們使用了 connect 函式為包裹起來的 Hello 元件的 props 適配了應用的狀態。

此時,執行專案,點選 + 或者 - 按鈕,即可看到 times 的次數會發生變化。

總結

至此,對於使用 TypeScript 編寫 React 應用應該有了一定的瞭解。其實寫法也比較固定,剛接觸的話可能有些地方容易出現問題,多寫幾個元件之後,應該就沒什麼問題了。 在編寫專案的過程中,create-react-app 自帶的 tslint 可能要求比較嚴嚴格,比如:

  • 在標籤裡不允許使用 lambda 表示式,在 tslint.json 檔案 rules 屬性中新增:"jsx-no-lambda": false 即可
  • 在匯入模組時,必須按照字母順序匯入,在 tslint.json 檔案 rules 屬性中新增:"ordered-imports": false 即可

還有很多別的配置,有需要的話,可以檢視文件:TSLint core rules


本文 Demo 地址

  • 本文 Counter Demo 是一個簡易的例子,可以用來作為入門參考:Counter Demo

  • 另外還寫了一個 TodoList 的例子,稍微更有難度一些,程式碼量和元件更多更詳細。有需要的朋友也可以參考一下:TodoList Demo

相關文章