使用 TypeScript
編寫 React
需要注意的規范
必須遵守的要求:
- 所有用到
jsx
語法的文件都需要以tsx
后綴命名 - 使用組件聲明時的
Component<P, S>
泛型參數聲明,來代替PropTypes
進行類型校驗
額外的代碼規范:
- 全局變量或者自定義的
window
對象屬性,統一在項目根下的global.d.ts
中進行聲明定義 - 對於項目中常用到的接口數據對象,最好在
types/
目錄下定義好其結構化類型聲明
創建項目
npx create-react-app typescript-react-app --scripts-version=react-scripts-ts
react-scripts-ts是一系列適配器,它利用標准的create-react-app工程管道並把TypeScript混入進來。
項目創建成功后,此時項目結構如下所示:
typescript-react-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 下創建一個 container目錄,新增 Counter 組件:
Counter.tsx
import * as React from 'react'; // 創建類型接口 export interface Iprops { value: number } // 使用接口代替 PropTypes 進行類型校驗:函數組件 const Counter = ({ value }: Iprops) => { return <p>Clicked: { value } times</p> } // 使用接口代替 PropTypes 進行類型校驗:使用類的方式 const Counter = ({ value }: Iprops) => { return <p>Clicked: { value } times</p> } export default Counter;
我們可以使用函數組件或者類組件,注意兩者的區別是類方式組件有react的生命周期,而函數組件沒有,建議函數組件來做純顯示
在 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 組件已經編寫並使用成功了。
進階:配合 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.ts
// 定義 State 結構類型 export type StoreState = number;
添加 actions-type
在 src 下創建 store目錄,在const.ts 文件中添加需要響應的消息類型
src/store/const.ts
// 定義增加 state 類型常量 export const INCREMENT = "INCREMENT"; export type INCREMENT_TYPE = typeof INCREMENT; // 定義減少 state 類型常量 export const DECREMENT = "DECREMENT"; export type DECREMENT_TYPE = typeof DECREMENT;
這里的const/type模式允許我們以容易訪問和重構的方式使用TypeScript的字符串字面量類型。 接下來,我們創建一些 actions 以及創建這些 actions 的函數
添加 actions
src/store/actions/index.ts
import {DECREMENT, DECREMENT_TYPE, INCREMENT, INCREMENT_TYPE} from '../const' export interface IINCREMENTAction { type: INCREMENT_TYPE; } export interface IDECREMENTAction { type: DECREMENT_TYPE; } // 定義 modifyAction 類型,包含 IINCREMENTAction 和 IDECREMENTAction 接口類型 export type ModifyAction = IINCREMENTAction | IDECREMENTAction; // 增加 state 次數的方法 export const increment = (): IINCREMENTAction => ({ type: INCREMENT, }) // 減少 state 次數的方法 export const decrement = (): IDECREMENTAction => ({ type: DECREMENT })
添加 reducer
我們的reducer將放在src/reducers/index.tsx文件里。 它的功能是保證增加操作會讓 times
加1,減少操作則要將 times
減1。
reducers/index.tsx
import { ModifyAction } from '../actions'; import { DECREMENT, INCREMENT } from '../const'; // 處理並返回 state export default (state = 0, action: ModifyAction): number => { switch (action.type) { case INCREMENT: return state + 1 case DECREMENT: return state - 1 default: return state } }
注意上面整個reducer只有一個reducer
,就是一個number
,如果需要多個reducer可以combineReducers組建多個reducer
import { combineReducers } from 'redux' // 一個state function count (state = 0, action: ModifyAction): number => { switch (action.type) { case INCREMENT: return state + 1 case DECREMENT: return state - 1 default: return state } function test (state = 0, action: ModifyAction): number => { switch (action.type) { ... default: return state } 這樣可以吧store變成一個對象來組合reducer = state const rootReducer = combineReducers({ count, test })
創建容器組件
創建一個 container
目錄,用來存放需要與數據交互的組件,新建 CounterCon.tsx
文件.
兩個關鍵點是初始的 Counter
組件和 react-redux
的 connect
函數。 connect
可以將我們的 Counter
組件轉換成一個容器,通過以下兩個函數:
- mapStateToProps將當前store里的數據以我們的組件需要的形式傳遞到組件。
- mapDispatchToProps利用dispatch函數,創建回調props將actions送到store。
import * as React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; import { decrement, increment } from '../store/actions'; import { StoreState } from '../types'; // 創建類型接口 export interface IProps { value: number, onIncrement: () => void, onDecrement: () => void } // 使用接口代替 PropTypes 進行類型校驗 class Counter extends React.PureComponent<IProps> { public componentWillMount () { // tslint:disable-next-line console.log(this.props) // 這里的prop是拿不到dispatch函數,因為組合高階函數的時候做了處理,沒有傳入dispatch,只有{value: 0, onDecrement: ƒ, onIncrement: ƒ} } 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> ) } } // 將 reducer 中的狀態插入到組件的 props 中 // 下面是單個reducer的時候,多個的時候需要選傳入哪個reducer // const { test, count } = state 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);
另外如果connect的時候不傳入數據的時候connect()(Test)
,組件的this.prop
只能拿到dispatch的函數,和外層父元素的props
,並不能拿到store
的數據,而且高階函數返回一個可以注入store的prop的組件,依然可以接受外層的props
,所以注意reducer
必須要注入,否則沒有,只有dispatch
,而且必須是一個對象!!!!!!
創建 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 { composeWithDevTools } from 'redux-devtools-extension' import App from './App'; import './index.css'; import reducer from './reducer'; import registerServiceWorker from './registerServiceWorker'; // 1、創建 store const store = createStore(reducer, composeWithDevTools()); ReactDOM.render( // 2、然后使用react-redux的Provider將props與容器連通起來 <Provider store={ store }> <App /> </Provider> , document.getElementById('root') as HTMLElement ); registerServiceWorker();
其中composeWithDevTools
是Chrome里面的調試工具
回到我們的 App.tsx 文件中。改寫如下:
App.tsx
import * as React from 'react'; import './App.css'; import Counter from './containers/Counter' import logo from './logo.svg'; class App extends React.Component { public render() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <h1 className="App-title">Welcome to React</h1> </header> <Counter/> </div> ); } } export default App;
注意,此時 Counter
不再需要 props
了,因為我們使用了 connect
函數為包裹起來的 Hello
組件的 props
適配了應用的狀態,當然也可以傳進去,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。
參考文檔
作者:mongofeng
鏈接:https://www.jianshu.com/p/2b981304cdd4
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。