原文地址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支持的遷移步驟,建立一個新的app並開啓typescript,先要執行如下命令:github
yarn create react-app app_name --typescript
#or
npx create-react-app app_name --typescript
複製代碼
基於Create React App模板建立的項目,針對javascript有幾個須要注意的變化typescript
yarn start執行階段將會編譯和運行你的應用,而且會生成一個和原有js版本應用相同的副本json
在咱們繼續往下展開以前,最好先中止開發服務,從新考慮一些針對Typescript和React的檢查工具。redux
引入檢查工具對於開發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"
]
}
}
複製代碼
簡單說明一下文件中的各個選項都是用來幹嗎的
咱們能夠在組件的props以及state上應用interface和type
當咱們傳遞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。對於類組件,咱們利用尖括號語法去分別應用咱們的props和state的interface。
export class MyForm extends React.Component<FormProps, FormState> {
...
}
複製代碼
注意:在只有state而沒有props的狀況下,props的位置能夠用{}或者object佔位,這兩個值都表示有效的空對象。
對於純函數組件,咱們能夠直接傳遞props interface
function MyForm(props: FormProps) {
...
}
複製代碼
按照約定,咱們通常會建立一個 **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是另一個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;
複製代碼
在typescript中咱們可使用 for...of和 for...in方法來進行循環遍歷。這兩個方法有一個很重要的區別:
for (let i in heardFrom) {
console.log(i); // "0", "1", "2",
}
for (let i of heardFrom) {
console.log(i); // "Search Engine", "Friend", "Other"
}
複製代碼
若是你但願好比onChange或者onClick事件利用語法工具能夠獲取明確的你所須要的事件。 能夠考慮下面這個例子,經過將光標懸浮在handleChange()方法上,咱們就能夠清晰的看到真實的事件類型React.ChangeEvent::
而後在咱們的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 store定義一個interface。定義合理的state結構將有利於你的團隊及更好的維護應用的狀態
這部分能夠在咱們先前討論過的 /src/types/index.tsx文件中完成,下面是一個試圖解決位置與身份認證的示例:
// src/types/index.tsx
export interface MyStore {
language: string;
country: string;
auth: {
authenticated: boolean;
username?: string;
};
}
複製代碼
全部的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的值
爲了簡化在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相對來講也是很是直觀
利用尖括號傳入類型聯同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)所須要的mapStateToProps和mapDispatchToProps
在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不只能夠提高代碼的可讀性,同時也會下降錯誤發生的可能性。