react與typescript搭配幹活就是不累(譯)

原文地址javascript

做者:Ross Bulat前端

注:本文並不是直譯java

一份用Typescript開發react和redux應用的指南node

typescript在加強react應用的穩定性,可讀性以及易管理性方面一直都處在很是重要的位置上,typescript爲React和其餘javascript前端框架逐步引入了更多的支持;從3.0版本和3.1版本以後顯著加強了許多功能。在過去,typescript集成到react應用中是一件很頭疼的任務,但在這篇文章中,我門就會去探討一種更爲直截了當的方式讓typescript和react更輕鬆的融合。react

Create React App如今已經內置支持了typescript,從react-scripts2.1開始,經過添加一個typescript選項,就能夠在一個新項目中開啓typescript,在探索如何將types和interface集成到React的props和state以前,咱們將會使用Create React App去構造一個基於react和typescript應用git

用Create React App建立typescript項目

Create React App官網有一個專門的頁面用來介紹項目建立過程以及給現有的項目添加typescript支持的遷移步驟,建立一個新的app並開啓typescript,先要執行如下命令:github

yarn create react-app app_name --typescript
  
  #or
  
  npx create-react-app app_name --typescript
複製代碼

基於Create React App模板建立的項目,針對javascript有幾個須要注意的變化typescript

  • 如今一份配置了typescript編譯器選項的 tsconfig.json 文件被引入了進來
  • .js文件如今要統一變爲以 .tsx 爲後綴的文件,這樣typescript編譯器就可以在編譯時識別出全部的.tsx文件
  • package.json包含了針對各類@types包的依賴,包括對node,jest,react和react-dom的支持,而且是開箱即用的
  • 一份叫作 react-app-env.d.ts的文件會去引用 react-scripts的類型,經過yarn start啓動本地開發服務,這個文件會自動生成。

yarn start執行階段將會編譯和運行你的應用,而且會生成一個和原有js版本應用相同的副本json

在咱們繼續往下展開以前,最好先中止開發服務,從新考慮一些針對Typescript和React的檢查工具。redux

下載tslint-react

引入檢查工具對於開發typescript和react應用來講很是有幫助,你能夠根據提示去獲得一個確切的類型,尤爲是事件(events)類型,檢查工具備極其嚴格的默認配置,因此咱們在安裝過程當中忽略一些規則。

注意:這些檢查工具經過NPM或者Yarn進行安裝

全局安裝typescript,tslint,tslint-react

yarn global add tslint typescript tslint-react
複製代碼

如今在你的項目目錄下,初始化tslint

tslint --init
複製代碼

上述命令會生成一個擁有一些默認配置選項的tslint.json文件,將文件內容替換成以下內容

{
    "defaultSeverity": "error",
    "extends": [
      "tslint-react"
    ],
    "jsRules": {
    },
    "rules": {
      "member-access": false,
      "ordered-imports": false,
      "quotemark": false,
      "no-console": false,
      "semicolon": false,
      "jsx-no-lambda": false
    },
    "rulesDirectory": [
    ],
    "linterOptions": {
      "exclude": [
        "config/**/*.js",
        "node_modules/**/*.ts"
      ]
    }
  }
複製代碼

簡單說明一下文件中的各個選項都是用來幹嗎的

  • defaultSeverity規定了錯誤處理的級別,error做爲默認值將會在你的IDE裏出現紅色的錯誤提示,然而warning將會展示橘黃色的警告提示
  • "extends": ["tslint-react"]: 咱們擴展的規則是基於已經刪除了的tslint-recommended庫,之因此沒有使用tslint-recommended庫,是由於該庫有些規則並無遵循React語法
  • "rules": {"rule-name": false, ...}: 咱們能夠在rules對象內忽略一些規則,好比,忽略member-access規則,以阻止tslint報出缺乏函數訪問類型的提示,由於在react中成員訪問關鍵字(public,privaye)並不經常使用,另一個例子,ordered-imports,這個規則會提示咱們根據字母順序排列咱們的import的語句,全部可用的規則能夠點擊這裏進行查看這裏
  • "linterOptions": {"exclude": [...]}: 在這裏咱們排除了全部在config目錄下的js後綴的文件和在node_modules目錄下的ts文件以免tslint的檢查

咱們能夠在組件的props以及state上應用interface和type

定義interface

當咱們傳遞props到組件中去的時候,若是想要使props應用interface,那就會強制要求咱們傳遞的props必須遵循interface的結構,確保成員都有被聲明,同時也會阻止未指望的props被傳遞下去。

interface能夠定義在組件的外部或是一個獨立文件,能夠像這樣定義一個interface

interface FormProps {
    first_name: string;
    last_name: string;
    age: number;
    agreetoterms?: boolean;
  }
複製代碼

這裏咱們建立了一個FormProps接口,包含一些值,agreeToterms後面跟着?,表明該成員是可選的,非必傳,咱們也能夠給組件的state應用一個interface

interface FormState {
      submitted?: boolean;
      full_name: string;
      age: number;
  }
複製代碼

注意:tslint過去會提示咱們給每個interface名稱前面加上一個i,好比IFormProps和IFormState。然而默認是不強制加的

給組件應用interface

咱們既能夠給類組件也能夠給無狀態組件應用interface。對於類組件,咱們利用尖括號語法去分別應用咱們的props和state的interface。

export class MyForm extends React.Component<FormProps, FormState> {
    ...
  }
複製代碼

注意:在只有state而沒有props的狀況下,props的位置能夠用{}或者object佔位,這兩個值都表示有效的空對象。

對於純函數組件,咱們能夠直接傳遞props interface

function MyForm(props: FormProps) {
    ...
  }
複製代碼

引入interface

按照約定,咱們通常會建立一個 **src/types/**目錄來將你的全部interface分組:

// src/types/index.tsx
  export interface FormProps {
    first_name: string;
    last_name: string;
    age: number;
    agreetoterms?: boolean;
  }
複製代碼

而後引入組件所須要的interface

// src/components/MyForm.tsx
  import React from 'react';
  import { StoreState } from '../types/index';
  ...
複製代碼

enums

枚舉Enums是另一個typescript有用的功能,假設咱們想針對 MyForm組件來定一個枚舉,而後對提交的表單值進行驗證

// define enum
  enum HeardFrom {
      SEARCH_ENGINE = "Search Engine",
      FRIEND = "Friend",
      OTHER = "Other"
  }
  //construct heardFrom array
  let heardFrom = [HeardFrom.SEARCH_ENGINE, 
                  HeardFrom.FRIEND, 
                  HeardFrom.OTHER];

  //get submitted form value
  const submitted_heardFrom = form.values.heardFrom;

  //check if value is valid
  heardFrom.includes(submitted_heardFrom)
    ? valid = true
    : valid = false;
複製代碼

iterables

在typescript中咱們可使用 for...offor...in方法來進行循環遍歷。這兩個方法有一個很重要的區別:

  • for...of方法會返回被迭代對象的鍵(key)的列表
  • for...in方法會返回被迭代對象的值(value)的列表
for (let i in heardFrom) {
   console.log(i); // "0", "1", "2",
  }
  for (let i of heardFrom) {
    console.log(i); // "Search Engine", "Friend", "Other"
  }
複製代碼

Typing Events

若是你但願好比onChange或者onClick事件利用語法工具能夠獲取明確的你所須要的事件。 能夠考慮下面這個例子,經過將光標懸浮在handleChange()方法上,咱們就能夠清晰的看到真實的事件類型React.ChangeEvent:

event type

而後在咱們的handleChange函數定義中傳入e這個參數的時候會用到這個類型

咱們也能夠給e對象中的name和value指定類型,經過下面的語法:

const {name, value}: {name: string; value: string;} = e.target;
複製代碼

若是你不知道對象該指定什麼類型,你可使用any類型,就像下面這樣

const {name, value}: any = e.target;
複製代碼

如今咱們已經學會了一些基本的示例,接下來一塊兒來看看typescript如何與redix搭配。探索typescript更多的功能

Redux with Typescript

Step1:給Store指定類型

首先,咱們想要給咱們的Redux store定義一個interface。定義合理的state結構將有利於你的團隊及更好的維護應用的狀態

這部分能夠在咱們先前討論過的 /src/types/index.tsx文件中完成,下面是一個試圖解決位置與身份認證的示例:

// src/types/index.tsx
  export interface MyStore {
    language: string;
    country: string;
    auth: {
        authenticated: boolean;
        username?: string;
    };
  }
複製代碼
Step2:定義action的類型以及actions

全部的action類型能夠用一種 const & type的模式來進行定義,咱們首先在 src/constants/index.tsx文件中定義action types:

// src/constants/index.tsx
  export const SET_LANGUAGE = 'INCREMENT_ENTHUSIASM';
  export type SET_LANGUAGE = typeof SET_LANGUAGE;
  export const SET_COUNTRY = 'SET_COUNTRY';
  export type SET_COUNTRY = typeof SET_COUNTRY;
  export const AUTHENTICATE = 'AUTHENTICATE';
  export type AUTHENTICATE = typeof AUTHENTICATE;
複製代碼

注意到如何讓咱們剛剛定義的常量被用做interface類型仍是字符串字面量,咱們會在後面進行使用講解

這些const & type所組成的對象如今能夠在src/actions/index.tsx文件中進行導入了,這裏咱們定義了action interface以及action自身,以及對它們都指定了類型

// src/actions/index.tsx
  import * as constants from '../constants';

  //define action interfaces
  export interface SetLanguage {
      type: constants.SET_LANGUAGE;
      language: string;
  }
  export interface SetCountry {
      type: constants.SET_COUNTRY;
      country: string;
  }
  export interface Authenticate{
      type: constants.AUTHENTICATE;
      username: string;
      pw: string;
  }

  //define actions
  export function setLanguage(l: string): SetLanguage ({ type: constants.SET_LANGUAGE, language: l }) export function setCountry(c: string): SetCountry ({ type: constants.SET_COUNTRY, country: c }) export function authenticate(u: string, pw: string): Authenticate ({ type: constants.SET_COUNTRY, username: u, pw: pw }) 複製代碼

在authenticate action中,咱們傳入了username和password兩個參數,兩個參數都是string類型,返回值也指定了類型,在這個示例中是Authenticate

在Authenticate interface內部,咱們也包括了有效的action所須要的username和pw的值

Step3:定義Reducers

爲了簡化在reducer中指定一個action type的過程,咱們能夠利用聯合類型,這個特性是在typescript1.4版本以後引入進來的,聯合類型容許咱們將兩種或兩種以上的類型合併爲一個類型

回到咱們的actions文件,給咱們表示位置的interface添加一個聯合類型

// src/actions/index.tsx
  export type Locality = SetLanguage | SetCountry;
複製代碼

如今咱們就能夠將Locality類型應用到咱們reducer函數中的action

// src/reducers/index.tsx
  import { Locality } from '../actions';
  import { StoreState } from '../types/index';
  import { SET_LANGUAGE, SET_COUNTRY, AUTHENTICATE} from '../constants/index';
  export function locality(state: StoreState, action: Locality): StoreState {
    switch (action.type) {
      case SET_LANGUAGE:
        return return { ...state, language: action.language};
      case SET_COUNTRY:
        return { ...state, language: action.country};
      case AUTHENTICATE:
        return { 
          ...state, 
          auth: {
              username: action.username, 
              authenticated: true 
            }
        };
    }
    return state;
  }
複製代碼

儘管已經所有指定了類型,這個reducer相對來講也是很是直觀

  • 這個命名爲locality的reducer,將state指定爲StoreState類型,以及將action指定爲Locality類型
  • 這個reducer將會返回一個StoreState類型的對象,若是並無匹配到任何的action就將原state返回
  • 咱們的 constant & type(常量和類型)對在這裏也被獲得應用,做爲action間切換的條件
Step4:建立初始化Store

利用尖括號傳入類型聯同createStore(),在index.ts文件中咱們能夠初始化store了

// src/index.tsx
  import { createStore } from 'redux';
  import { locality } from './reducers/index';
  import { StoreState } from './types/index';
  const store = createStore<StoreState>(locality, {
    language: 'British (English)',
    country: 'United Kingdom',
    auth: {
        authenticated: false
    }
  });
複製代碼

已經快要完成了,如今已經覆蓋了集成typescript到redux中的大部分步驟了,再堅持一下,讓咱們來看一下容器組件(container component)所須要的mapStateToPropsmapDispatchToProps

Mapping State and Dispatch

mapStateToProps內部,記得將state參數設定爲StoreState類型,第二個參數ownProps也能夠指定一個props的interface:

//mapStateToProps example
  import { StoreState } from '../types/index';
  interface LocalityProps = {
      country: string;
      language: string;
  }
  export function mapStateToProps(state: StoreState, ownProps: LocalityProps) {
    return {
      language: state.language,
      country: state.country,
    }
  }
複製代碼

mapDispatchToProps有些不一樣,咱們利用尖括號想Dispatch方法中傳入一個interface,而後,在返回代碼塊中dispatch咱們Locality類型的action:

//mapDispatchToProps example
  export function mapDispatchToProps(dispatch: Dispatch<actions.Locality>) {
      return {
          setLanguage: (l: string) => 
              dispatch(actions.setLanguage(l)),
          
          setCountry: (c: string) => 
              dispatch(actions.setCountry(c))
      }
  }
複製代碼

最後,咱們就能夠和組件進行鏈接

export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
  ...
複製代碼

總結

這篇文章介紹了typescript和react如何聯合以及如何利用tslint-react進行更加平緩的開發。咱們已經瞭解到如何在組件中讓props和state應用interface,一樣也瞭解到了在Typescript中如何處理事件。最終,咱們瞭解了typescript如何集成到Redux中。

將typescript集成到react項目中,的確會增長一些額外的成本,但隨着應用範圍的擴大,支持typescript語言必定會增長項目的維護性和可管理性

使用typescript會促進模塊化和代碼分隔,記住,隨着項目的擴大。若是你發現了維護性方面的問題,typescript不只能夠提高代碼的可讀性,同時也會下降錯誤發生的可能性。

相關文章
相關標籤/搜索