React与Typescript整合

Posted zhangzhengsmiling

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React与Typescript整合相关的知识,希望对你有一定的参考价值。

 0. Typescript

  Typescript对于前端来说可以说是越来越重要了,前端的很多项目都用Typescript进行了重构。这主要得益于Typescript有比较好的类型支持,在编码的过程中可以很好地做一些类型推断(主要是编辑器会有代码提示,就很舒服)。再者Typescript的语法相较于javascript更加严谨,有更好的ES6的支持,这些特性使得使用ts编码更加高效,尽量避免javascript中容易造成模糊的坑点。 我最近也正在学Typescript的一些知识,无奈本人实习所在的公司貌似暂时还不打算使用typescript,无奈只好自己琢磨,尝试将typescript与react进行整合,花了挺长时间的,关键在于typescript中需要对变量进行约束。

1. react的typescript版本项目初始化

  这个没有好说的,使用react脚手架就可以初始化react项目,默认情况下安装的是javascript版本的项目,在脚手架命令中加上typescript的配置参数,就可以初始化typescript版本的react项目啦。

create-react-app react-todo-ts --typescript

2. react-todo-ts

  本次主要通过一个简单的Todo应用,对于在React中整合typescript的流程有一个简单的认识。我采用的目录结构比较简单(ps:按照常理,一个简单的Todo应用其实没有必要整的这么复杂,也没有必要使用redux增加项目的复杂度,不过此处只是做一个演示,而在redux中也是需要使用typescript的类型声明的,否则可能无法通过编译)’

目录结构如下:

技术图片

做个简单的说明:

  • components中主要存放组件

  • store中包含了一些redux相关的代码

  • types只用存放公用的类型定义以及接口

  • index.tsx是项目默认的入口文件

package.json文件的说明:

其中有几个声明文件是不需要的:@types/antd,@types/redux-thunk这两个声明文件是不需要的,它们的类型声明文件在antd和redux-thunk库中已经存在。(另外,本来想使用redux-thunk模拟一下异步请求的,但在整合的过程中稍微还有点问题,因此此版本并没有异步action的整合)。

  "name": "react-todo-ts",
  "version": "0.1.0",
  "private": true,
  "dependencies": 
    "@types/antd": "^1.0.0",
    "@types/jest": "24.0.17",
    "@types/node": "12.7.2",
    "@types/react": "16.9.2",
    "@types/react-dom": "16.8.5",
    "@types/react-redux": "^7.1.2",
    "@types/redux-thunk": "^2.1.0",
    "antd": "^3.21.4",
    "babel-plugin-import": "^1.12.0",
    "react": "^16.9.0",
    "react-dom": "^16.9.0",
    "react-redux": "^7.1.0",
    "react-scripts": "3.1.1",
    "redux": "^4.0.4",
    "redux-thunk": "^2.3.0",
    "typescript": "3.5.3"
  ,
  "scripts": 
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  ,
  "eslintConfig": 
    "extends": "react-app"
  ,
  "browserslist": 
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  

?

组件拆分说明:

技术图片

技术图片

3.typescript与antd整合

此处选取Header组件的代码做说明

  1. Component类的变化

    首先,变化的是Component类,我们可以通过泛型的方式约束组件的state和props的类型

interface IHeaderProps 
  todoList:ITodo[];
  addTodoAction: typeof addTodoAction

?
interface IHeaderState 
  todoText:string;

?
class Header extends Component<IHeaderProps, IHeaderState> 
  state = 
    todoText: ‘‘
  
    ...
    ...
  render() 
    return (
      <Row>
        <Col span=16>
          <Input placeholder="please input todo:" value=this.state.todoText onChange=(e) => this.handleChange(e) onKeyDown=(e) => this.handleKeyDown(e)></Input>
        </Col>
        <Col span=8>
          <Button disabled=this.state.todoText.trim() === ‘‘ type=‘primary‘ style= marginLeft: ‘50%‘, transform: ‘translateX(-50%)‘  onClick=() => this.handleAdd()>添加</Button>
        </Col>
      </Row>
    )
  

此处通过Component<IHeaderProps, IHeaderState>约束Header组件中的props和state属性,这样做以后,Header中的props属性必须满足IHeaderProps接口,state必须满足IHeaderState接口

  1. 事件交互部分代码的变化

    handleChange = (e:ChangeEvent<htmlInputElement>) => 
        const  value  = e.currentTarget;
        this.setState( todoText: value );
      
    ?
      handleAdd = () => 
        const  todoText  = this.state;
        if(todoText.trim() === ‘‘) 
          return;
        
        this.props.addTodoAction(
          content: todoText,
          done: false
        );
        this.setState( todoText: ‘‘ )
      
    ?
      handleKeyDown = (e:KeyboardEvent<HTMLInputElement>) => 
        if(e.keyCode === 13) 
          console.log(e.keyCode);
          this.handleAdd();
        
      
      
       render() 
        return (
          <Row>
            <Col span=16>
              <Input placeholder="please input todo:" value=this.state.todoText onChange=(e) => this.handleChange(e) onKeyDown=(e) => this.handleKeyDown(e)></Input>
            </Col>
            <Col span=8>
              <Button disabled=this.state.todoText.trim() === ‘‘ type=‘primary‘ style= marginLeft: ‘50%‘, transform: ‘translateX(-50%)‘  onClick=() => this.handleAdd()>添加</Button>
            </Col>
          </Row>
        )
      

    在ts中我们定义一个函数时必须要制定函数参数的类型,当我们在定义handler函数时,需要用到event对象时,我们又该如何声明event对象的类型呢?

    最开始的时候,我一般为了避免报错,不管三七二十一就是一个any声明,但这样其实就失去了类型推断的意义。

    在本项目中react的事件类型分为两类:

    1. antd组件上的事件类型

      antd组件中的事件类型一般在antd的库中都会定义,但是有些组件与原生的事件定义类型一致

    2. 原生组件上的事件类型

      原生组件的事件类型一般定义在@types/react库中,可以从react库中引入事件类型,一般原生事件类型的命名方式是通过(事件名称<元素类型>)的方式来声明的

      技术图片

   在vscode下,当你不确定事件类型的时候,hover上去会有函数签名提示,就可以比较方便地确定事件类型了 

技术图片

4. typescript与redux整合

主要针对todoList的操作进行

  1. 对于todo的结构定义一个接口

    export interface ITodo 
      content:String;
      done:boolean;
    
  2. 确定对todoList的操作(添加todo,删除todo,修改完成状态),然后定义相关的action

    import  ADD_TODO, DELETE_TODO, CHANGE_TODO_STATUS  from ‘./action-types‘;
    ?
    import  ITodo  from ‘../types‘;
    ?
    export const addTodoAction = (todo:ITodo):AddTodoAction => ( type: ADD_TODO, todo );
    export const deleteTodoAction = (index:number):DeleteTodoAction => ( type: DELETE_TODO, index );
    export const changeTodoStatusAction = (index:number):ChangeTodoStatusAction => ( type: CHANGE_TODO_STATUS, index );
    ?
    ?
    export type AddTodoAction = 
      type: typeof ADD_TODO,
      todo: ITodo;
    
    ?
    export type DeleteTodoAction = 
      type: typeof DELETE_TODO,
      index:number;
    
    ?
    export type ChangeTodoStatusAction = 
      type: typeof CHANGE_TODO_STATUS,
      index:number;
    
  1. 定义todoReducer,传入todoReducer的action有三种可能,从actions.ts中将action的类型导入

    import  ADD_TODO, DELETE_TODO, CHANGE_TODO_STATUS  from ‘./action-types‘;
    import  ITodo   from ‘../types‘;
    import  AddTodoAction, DeleteTodoAction, ChangeTodoStatusAction  from ‘./actions‘
    ?
    const initTodoList:ITodo[] = [];
    ?
    export const todoReducer = (todos:ITodo[] = initTodoList, action:AddTodoAction | DeleteTodoAction | ChangeTodoStatusAction) => 
      switch(action.type) 
        case ADD_TODO:
          // 由于action传入的类型有三种可能,没法准确判断action类型。但经过case判断以后,action的类型应当是确定的,因此在此处我使用了类型断言的方式,将action断言为AddTodoAction(下同)
          return [(action as AddTodoAction).todo, ...todos];
        case DELETE_TODO:
          return todos.filter((todo, index) => index !== (action as DeleteTodoAction).index);
        case CHANGE_TODO_STATUS:
          const nextTodo:ITodo[] = [...todos];
          let target:ITodo = nextTodo.find((todo, index) => index === (action as ChangeTodoStatusAction).index) as ITodo;
          target.done = !target.done;
          return nextTodo;
        default:
          return todos;
      
    
    
  2. store中暴露store工厂函数,获取store类型的时候可以通过ReturnType获取

    import  todoReducer  from ‘./reducers‘;
    import  combineReducers, createStore, applyMiddleware from ‘redux‘;
    import thunk from ‘redux-thunk‘;
    ?
    const rootReducer = combineReducers(
      todoList: todoReducer
    )
    ?
    export type RootState = ReturnType<typeof rootReducer>
    // 向外暴露store工厂
    export function configStore() 
      return createStore(
        rootReducer,
        applyMiddleware(thunk)
      );
    

5. react-redux整合

通过react-redux分离依赖的方式与javascript版本没有太大的区别|

  1. 使用provider高阶组件包裹App组件

    import React from ‘react‘;
    import ReactDom from ‘react-dom‘;
    import ‘antd/dist/antd.css‘
    ?
    import  Provider  from ‘react-redux‘;
    import App from ‘./components/app‘;
    import  configStore  from ‘./store‘;
    ?
    const store = configStore();
    ?
    const Root = () => 
      return (
        <Provider store=store>
          <App/>
        </Provider>
      )
    
    ?
    ReactDom.render(
      (
        <Root/>
      ),
      document.querySelector(‘#root‘)
    );
    ?
  2. 内部组件引入,主要的不同点在于引入时需要将RootState的类型一同引入,在定义mapStateToProps函数时需要定义参数的类型。

    import React,  Component  from ‘react‘;
    import  connect  from ‘react-redux‘;
    import  Row, Col, Checkbox, Button, Empty, message  from ‘antd‘;
    ?
    import  RootState  from ‘../../store‘;
    import  ITodo  from ‘../../types‘;
    import  deleteTodoAction, changeTodoStatusAction  from ‘../../store/actions‘;
    ?
    interface IListProp 
      todoList:ITodo[];
      deleteTodoAction: typeof deleteTodoAction;
      changeTodoStatusAction:typeof changeTodoStatusAction;
    
    ?
    class List extends Component<IListProp> 
    ?
      handleChange = (index:number) =>  
        this.props.changeTodoStatusAction(index);
      
    ?
      handleDelete = async (index:number) => 
        await this.props.deleteTodoAction(index);
        message.success("删除成功", 0.5);
      
    ?
      render() 
        const  todoList  = this.props;
        return (
          <div>
          
            todoList.length ? (
              <div>
                
                  todoList.map((todo, index) => (
                   <Row key=index>
                     <label>
                        <Col span=1>
                          <Checkbox checked=todo.done onChange=() =>  this.handleChange(index) ></Checkbox>
                        </Col>
                        <Col span=20>
                          <span style= textDecoration: todo.done ? ‘line-through‘ : ‘none‘ >
                            
                              todo.content
                            
                          </span>
                        </Col>
                        <Col span=3 style=marginTop: ‘10px‘>
                          <Button type=‘danger‘ size=‘small‘ onClick=() => this.handleDelete(index)>删除</Button>
                        </Col>
                     </label>
                   </Row>
                  ))
                
              </div>
            )
            :
            (<Empty/>)
          
          </div>
        )
      
    
    ?
    const mapStateToProps = (state:RootState) => (
      todoList: state.todoList,
    )
    ?
    export default connect(
      mapStateToProps,
      
        deleteTodoAction,
        changeTodoStatusAction
      
    )(List);
    ?

6. 异步action

redux本身并不支持异步action,可是在使用的时候往往是需要发送异步请求的。在整合的过程中,存在一些问题,在View层通过事件发送一个异步action后,如何活的对应的promise状态,然后根据promise的状态做出相应的响应,可能还需要在看一看。

---------------------------------------------------------------------------------------

项目源码请戳--> https://github.com/zhangzhengsmiling/React-Todo-typescript.git

---------------------------------------------------------------------------------------

以上是关于React与Typescript整合的主要内容,如果未能解决你的问题,请参考以下文章

React Navigation + TypeScript 错误:类型“EventStackParams”不满足约束“Record<string, object |未定义>'

TypeScript入门学习之路

Chutzpah - 使用 jasmine 和 TypeScript 进行 AngularJS 测试

如何使用 sequelize-typescript 从单个字段中引用多个模型?

LayaBox---TypeScript---JSX

TypeScript `infer` 关键字