本文詳細介紹瞭如何使用 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-redux
的 connect
函式。 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