码迷,mamicode.com
首页 > 其他好文 > 详细

React与Typescript整合

时间:2019-08-18 18:00:05      阅读:130      评论:0      收藏:0      [点我收藏+]

标签:java   针对   conf   enc   plain   javascrip   签名   span   component   

 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整合

标签:java   针对   conf   enc   plain   javascrip   签名   span   component   

原文地址:https://www.cnblogs.com/zhangzhengsmiling/p/react-todo-typescript.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!