原文地址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不只能够提高代码的可读性,同时也会下降错误发生的可能性。