TypeScript + React + Redux 的個(gè)人總結(jié)

使用 TypeScript 編寫 React 需要注意的規(guī)范

必須遵守的要求:

  • 所有用到 jsx 語法的文件都需要以 tsx 后綴命名
  • 使用組件聲明時(shí)的 Component<P, S> 泛型參數(shù)聲明终议,來代替 PropTypes進(jìn)行類型校驗(yàn)

額外的代碼規(guī)范:

  • 全局變量或者自定義的 window 對(duì)象屬性汇竭,統(tǒng)一在項(xiàng)目根下的 global.d.ts 中進(jìn)行聲明定義
  • 對(duì)于項(xiàng)目中常用到的接口數(shù)據(jù)對(duì)象,最好在 types/ 目錄下定義好其結(jié)構(gòu)化類型聲明

創(chuàng)建項(xiàng)目

npx create-react-app typescript-react-app --scripts-version=react-scripts-ts

react-scripts-ts是一系列適配器穴张,它利用標(biāo)準(zhǔn)的create-react-app工程管道并把TypeScript混入進(jìn)來细燎。

項(xiàng)目創(chuàng)建成功后,此時(shí)項(xiàng)目結(jié)構(gòu)如下所示:

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特定的選項(xiàng)皂甘。
  • tslint.json保存了要使用的代碼檢查器的設(shè)置玻驻,TSLint。
  • package.json包含了依賴偿枕,還有一些命令的快捷方式璧瞬,如測(cè)試命令,預(yù)覽命令和發(fā)布應(yīng)用的命令渐夸。
  • public包含了靜態(tài)資源如HTML頁面或圖片嗤锉。除了index.html文件外,其它的文件都可以刪除墓塌。
  • src包含了TypeScript和CSS源碼档冬。index.tsx是強(qiáng)制使用的入口文件。

運(yùn)行項(xiàng)目

先運(yùn)行項(xiàng)目桃纯,看看是否能夠正常啟動(dòng),如果可以披坏,說明項(xiàng)目創(chuàng)建沒有問題态坦。 運(yùn)行命令:

$ npm run start

# 或者運(yùn)行 yarn run start

React 配合 TypeScript 的基本使用

在當(dāng)前項(xiàng)目中,可以看到 index.tsx 和 App.jsx 文件中已經(jīng)使用了 TypeScript棒拂,我們現(xiàn)在自己來用 TypeScript 編寫一個(gè) React 組件吧伞梯。

定義一個(gè) Counter 組件

我們?cè)?src 下創(chuàng)建一個(gè) container目錄,新增 Counter 組件:

Counter.tsx

import * as React from 'react';


// 創(chuàng)建類型接口
export interface Iprops {
    value: number
}

// 使用接口代替 PropTypes 進(jìn)行類型校驗(yàn):函數(shù)組件
const Counter = ({ value }: Iprops) => {
    return <p>Clicked: { value } times</p>
}

// 使用接口代替 PropTypes 進(jìn)行類型校驗(yàn):使用類的方式
const Counter = ({ value }: Iprops) => {
    return <p>Clicked: { value } times</p>
}

export default Counter;

我們可以使用函數(shù)組件或者類組件帚屉,注意兩者的區(qū)別是類方式組件有react的生命周期谜诫,而函數(shù)組件沒有,建議函數(shù)組件來做純顯示

在 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;

運(yùn)行項(xiàng)目:npm run start攻旦,可以看到瀏覽器中展示出了 Clicked: 0 times喻旷,說明我們第一個(gè) Counter 組件已經(jīng)編寫并使用成功了。

進(jìn)階:配合 Redux 進(jìn)行使用

安裝項(xiàng)目需要的插件

安裝redux和react-redux以及它們的類型文件做為依賴牢屋。

$ npm install -S redux react-redux @types/react-redux

這里我們不需要安裝@types/redux且预,因?yàn)镽edux已經(jīng)自帶了聲明文件(.d.ts文件)槽袄。

定義應(yīng)用的狀態(tài) State

一般會(huì)將常用的結(jié)構(gòu)類型存放到 /types 目錄下。所以我們?cè)?src 目錄下新建 types 目錄锋谐。此時(shí)項(xiàng)目中只有一個(gè) state遍尺,就是 Counter 中的點(diǎn)擊次數(shù),所以就沒有使用借口來作為約束涮拗,而是直接使用了 type乾戏。

type/index.ts

// 定義 State 結(jié)構(gòu)類型
export type StoreState = number;

添加 actions-type

在 src 下創(chuàng)建 store目錄,在const.ts 文件中添加需要響應(yīng)的消息類型

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模式允許我們以容易訪問和重構(gòu)的方式使用TypeScript的字符串字面量類型三热。 接下來鼓择,我們創(chuàng)建一些 actions 以及創(chuàng)建這些 actions 的函數(shù)

添加 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 次數(shù)的方法
export const increment = (): IINCREMENTAction => ({
  type: INCREMENT,
})

// 減少 state 次數(shù)的方法
export const decrement = (): IDECREMENTAction => ({
  type: DECREMENT
})

添加 reducer

我們的reducer將放在src/reducers/index.tsx文件里康铭。 它的功能是保證增加操作會(huì)讓 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
    }
}

注意上面整個(gè)reducer只有一個(gè)reducer从藤,就是一個(gè)number催跪,如果需要多個(gè)reducer可以combineReducers組建多個(gè)reducer

import { combineReducers } from 'redux'

// 一個(gè)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變成一個(gè)對(duì)象來組合reducer = state
 const rootReducer = combineReducers({
   count,
   test
})

創(chuàng)建容器組件

創(chuàng)建一個(gè) container 目錄,用來存放需要與數(shù)據(jù)交互的組件夷野,新建 CounterCon.tsx 文件.

兩個(gè)關(guān)鍵點(diǎn)是初始的 Counter 組件和 react-reduxconnect 函數(shù)懊蒸。 connect 可以將我們的 Counter 組件轉(zhuǎn)換成一個(gè)容器,通過以下兩個(gè)函數(shù):

  • mapStateToProps將當(dāng)前store里的數(shù)據(jù)以我們的組件需要的形式傳遞到組件悯搔。
  • mapDispatchToProps利用dispatch函數(shù)骑丸,創(chuàng)建回調(diào)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';


// 創(chuàng)建類型接口
export interface IProps {
  value: number,
  onIncrement: () => void,
  onDecrement: () => void
}

// 使用接口代替 PropTypes 進(jìn)行類型校驗(yàn)
 class Counter extends React.PureComponent<IProps> {
  public componentWillMount () {
    // tslint:disable-next-line
    console.log(this.props) // 這里的prop是拿不到dispatch函數(shù)妒貌,因?yàn)榻M合高階函數(shù)的時(shí)候做了處理通危,沒有傳入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 中的狀態(tài)插入到組件的 props 中
// 下面是單個(gè)reducer的時(shí)候灌曙,多個(gè)的時(shí)候需要選傳入哪個(gè)reducer
// const { test, count } = state
const mapStateToProps = (state: StoreState): { value: number } => ({
  value: state
})

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

// 使用 connect 高階組件對(duì) Counter 進(jìn)行包裹
export default connect(mapStateToProps, mapDispatchToProps)(Counter);

另外如果connect的時(shí)候不傳入數(shù)據(jù)的時(shí)候connect()(Test)菊碟,組件的this.prop只能拿到dispatch的函數(shù),和外層父元素的props在刺,并不能拿到store的數(shù)據(jù)逆害,而且高階函數(shù)返回一個(gè)可以注入store的prop的組件,依然可以接受外層的props蚣驼,所以注意reducer必須要注入魄幕,否則沒有,只有dispatch,而且必須是一個(gè)對(duì)象!!!!!!

創(chuàng)建 store

讓我們回到src/index.tsx。 要把所有的東西合到一起颖杏,我們需要?jiǎng)?chuàng)建一個(gè)帶初始狀態(tài)的store纯陨,并用我們所有的reducers來設(shè)置它。 并且使用 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、創(chuàng)建 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里面的調(diào)試工具

回到我們的 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;

注意,此時(shí) Counter不再需要 props 了机久,因?yàn)槲覀兪褂昧?connect 函數(shù)為包裹起來的 Hello 組件的 props 適配了應(yīng)用的狀態(tài)臭墨,當(dāng)然也可以傳進(jìn)去,props也可以拿到的膘盖。

此時(shí)胧弛,運(yùn)行項(xiàng)目,點(diǎn)擊 + 或者 - 按鈕侠畔,即可看到 times 的次數(shù)會(huì)發(fā)生變化结缚。

總結(jié)

至此,對(duì)于使用 TypeScript 編寫 React 應(yīng)用應(yīng)該有了一定的了解软棺。其實(shí)寫法也比較固定红竭,剛接觸的話可能有些地方容易出現(xiàn)問題,多寫幾個(gè)組件之后喘落,應(yīng)該就沒什么問題了茵宪。在編寫項(xiàng)目的過程中,create-react-app 自帶的 tslint 可能要求比較嚴(yán)嚴(yán)格瘦棋,比如:

  • 在標(biāo)簽里不允許使用 lambda 表達(dá)式稀火,在 tslint.json 文件 rules 屬性中添加:"jsx-no-lambda": false 即可
  • 在導(dǎo)入模塊時(shí),必須按照字母順序?qū)攵呐螅?tslint.json 文件 rules 屬性中添加:"ordered-imports": false 即可

還有很多別的配置凰狞,有需要的話,可以查看文檔:TSLint core rules沛慢。

參考文檔

[使用 TypeScript + React + Redux 進(jìn)行項(xiàng)目開發(fā)(入門篇赡若,附源碼]

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市团甲,隨后出現(xiàn)的幾起案子斩熊,更是在濱河造成了極大的恐慌,老刑警劉巖伐庭,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異分冈,居然都是意外死亡圾另,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門雕沉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來集乔,“玉大人,你說我怎么就攤上這事∪怕罚” “怎么了尤溜?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長汗唱。 經(jīng)常有香客問我宫莱,道長,這世上最難降的妖魔是什么哩罪? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任授霸,我火速辦了婚禮,結(jié)果婚禮上际插,老公的妹妹穿的比我還像新娘碘耳。我一直安慰自己,他們只是感情好框弛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布辛辨。 她就那樣靜靜地躺著,像睡著了一般瑟枫。 火紅的嫁衣襯著肌膚如雪斗搞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天力奋,我揣著相機(jī)與錄音榜旦,去河邊找鬼。 笑死景殷,一個(gè)胖子當(dāng)著我的面吹牛溅呢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播猿挚,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼咐旧,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了绩蜻?” 一聲冷哼從身側(cè)響起铣墨,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎办绝,沒想到半個(gè)月后伊约,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡孕蝉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年屡律,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片降淮。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡超埋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情霍殴,我是刑警寧澤媒惕,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站来庭,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏巾腕。R本人自食惡果不足惜面睛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一叁鉴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧佛寿,春花似錦幌墓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至弹渔,卻和暖如春胳施,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肢专。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來泰國打工舞肆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人博杖。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓椿胯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親剃根。 傳聞我的和親對(duì)象是個(gè)殘疾皇子哩盲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容