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

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

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

时间: 2024-11-09 20:52:25

React与Typescript整合的相关文章

从零配置webpack(react+less+typescript+mobx)

本文目标 从零搭建出一套支持react+less+typescript+mobx的webpack配置 最简化webpack配置 首页要初始化yarn和安装webpack的依赖 yarn init -y yarn add webpack webpack-cli -D 根目录下新建webpack.config.js文件,内容如下 const path = require('path'); module.exports = { mode: 'development', // 入口 这里应用程序开始执行

【学习】reactjs(一)——使用npm创建react项目并整合elementUI

在实习的过程中了解了react,所以打算使用react搭建一个属于自己的页面,如有问题请教正,谢谢. Github:https://github.com/yclxt/react-elementUI 工具: 使用工具:nodejs:Jetbrains WebStorm nodejs的安装和配置环境变量这里就不写了. 创建过程: 命令行安装全局create-react-app脚手架工具:npm install -g create-react-app 定位到工作目录下,创建项目:create-reac

[React Native] Up &amp; Running with React Native &amp; TypeScript

Create a new application with Typescript: react-native init RNTypeScript --template typescript Then: cd RNTypeScript Run: node ./setup.js 原文地址:https://www.cnblogs.com/Answer1215/p/11299694.html

umi + react+ mobx+ typescript项目

umi官网 :https://umijs.org/  支付宝当家大牛云谦搞得框架,不用自己配webpack babel那一堆烦人的东西.而且它约定了路由的规则.非常简单容易上手. mobx  的官网:https://cn.mobx.js.org/   用mobx而不用dva主要是因为 dva的state是一个对象,项目过大对象会巨长 ,而mobx则更灵活.本项目中主要用到了mobx 的如下api 项目目录结构: 原文地址:https://www.cnblogs.com/misswho/p/915

Nodejs生态圈的TypeScript+React

基于Nodejs生态圈的TypeScript+React开发入门教程 基于Nodejs生态圈的TypeScript+React开发入门教程 概述 本教程旨在为基于Nodejs npm生态圈的前端程序开发提供入门讲解. Nodejs是什么 Nodejs是一个高性能JavaScript脚本运行环境,内部基于Chrome V8脚本引擎.它相当于把在浏览器中执行JavaScript脚本的功能抽取出来,作为一个单独的程序,可在桌面端命令行等环境中使用. NPM是什么 NPM是nodejs包管理器(node

【react】使用 create-react-app 构建基于TypeScript的React前端架构----上

写在前面 一直在探寻,那优雅的美:一直在探寻,那精湛的技巧:一直在探寻,那简单又直白,优雅而美丽的代码. ------ 但是在JavaScript的动态类型.有时尴尬的自动类型转换,以及 “0 == false” 是true的尴尬, 你知道 “[undefined] == 0“ 是什么返回结果吗?再然后 function add (a, b) { return a+b } add ("1", 2) // 12 这个结果是返回是12. 还记得某个深夜,当你望着万行代码流出千行热泪的时候吗

初学react + redux

react + redux结合开发: 1,什么是redux,为什么要用redux: 在用react开发应用的时候随着项目的不断复杂化,管理不断变化的 state 非常困难.如果一个 model 的变化会引起另一个 model 变化, 那么当 view 变化时,就可能引起对应 model 以及另一个 model 的变化,依次地,可能会引起另一个 view 的变化.直至你搞不清楚到底发生了什么. state 在什么时候,由于什么原因,如何变化已然不受控制.所以redux出现了,Redux 试图让 s

vue和react的区别

react和vue都是做组件化的,整体的功能都类似,但是他们的设计思路是有很多不同的.使用react和vue,主要是理解他们的设计思路的不同. 数据是不是可变的 react整体是函数式的思想,把组件设计成纯组件,状态和逻辑通过参数传入,所以在react中,是单向数据流,推崇结合immutable来实现数据不可变. react在setState之后会重新走渲染的流程,如果shouldComponentUpdate返回的是true,就继续渲染,如果返回了false,就不会重新渲染,PureCompo

vue 和react的区别

1.数据是不是可变的 react整体是函数式的思想,把组件设计成纯组件,状态和逻辑通过参数传入,所以在react中,是单向数据流,推崇结合immutable来实现数据不可变. react在setState之后会重新走渲染的流程,如果shouldComponentUpdate返回的是true,就继续渲染,如果返回了false,就不会重新渲染,PureComponent就是重写了shouldComponentUpdate,然后在里面作了props和state的浅层对比. 而vue的思想是响应式的,也