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

相关文章
相关标签/搜索