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整合的更多相关文章

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

    本文目标 从零搭建出一套支持react+less+typescript+mobx的webpack配置 最简化webpack配置 首页要初始化yarn和安装webpack的依赖 yarn init -y ...

  2. React Hooks Typescript 开发的一款 H5 移动端 组件库

    CP design 使用 React hooks Typescript 开发的一个 H5 移动端 组件库 English | 简体中文 badge button icon CP Design Mobi ...

  3. 转换 React 为TypeScript

    转换 React 为TypeScript JavaScript import React from 'react'; import PropTypes from 'prop-types'; class ...

  4. 从零构建一个react+webpack+typescript的应用

    今天要完成在windows下从零开始构建一个react应用的任务 首先,新建一个文件夹,然后在该文件夹下使用命令npm init 初始化一个node项目. 然后安装所需依赖, npm i react ...

  5. React Native 项目整合 CodePush 全然指南

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/y4x5M0nivSrJaY3X92c/article/details/81976844 作者 | 钱 ...

  6. Jest+Enzyme React js/typescript测试环境配置案例

    本文案例github:https://github.com/axel10/react-jest-typescript-demo 配置jest的react测试环境时我们可以参考官方的配置教程: http ...

  7. 超简单的react和typescript和引入scss项目搭建流程

    1.首先我们先创建一个react项目,react官网也有react项目搭建的命令 npx create-react-app my-app cd my-app 2.安装我们项目需要的样式依赖,这个项目我 ...

  8. Electron结合React和TypeScript进行开发

    目录 结合React+TypeScript进行Electron开发 1. electron基本简介 为什么选择electron? 2. 快速上手 2.1 安装React(template为ts) 2. ...

  9. typescript整合到vue中的详细介绍,ts+vue一梭子

    通过vue-cli命令行安装vue项目,注意不要eslint 安装依赖 cnpm install typescript --save-dev cnpm install ts-loader --save ...

随机推荐

  1. HDU 5687 Problem C ( 字典树前缀增删查 )

    题意 : 度熊手上有一本神奇的字典,你可以在它里面做如下三个操作: 1.insert : 往神奇字典中插入一个单词 2.delete: 在神奇字典中删除所有前缀等于给定字符串的单词 3.search: ...

  2. 舞蹈课(dancingLessons)

    有n个人参加一个舞蹈课.每个人的舞蹈技术由整数ai来决定.在舞蹈课的开始,他们从左到右站成一排.当这一排中至少有一对相邻的异性时,舞蹈技术相差最小的那一对会出列并开始跳舞.如果相差最小的不止一对,那么 ...

  3. python 获取list某个元素下标

    index() 函数用于从列表中找出某个值第一个匹配项的索引位置. list.index(x, start, end) #start end 指示搜索的起始和结尾位置,缺省为整个数组 x-- 查找的对 ...

  4. Springboot-H2DB

    为什么在Springboot中用H2DB 用Springboot开发需要连接数据库的程序的时候,使用H2DB作为内存数据库可以很方便的调试程序. 怎么用 1.加入依赖 <dependency&g ...

  5. 关于Java协变性的思考

    简而言之,如果A IS-A B,那么A[] IS-A B[]. 举例:现在有类型Person.Employee和Student.Employee 是一个(IS-A) Person,Student是一个 ...

  6. jmeter的日常特殊参数化

    1.map转译符号:   如果///Mobile///:///18888888888///   需要再参数化请这样做,////Mobile////://///${Mobile}/////   2.in ...

  7. Linux内核调试方法总结之backtrace

    backtrace [用途]用户态或者内核态程序异常退出时回溯堆栈信息 [原理]通过对当前堆栈的分析,回溯上层函数在当前栈中的帧地址,直至顶层函数.帧地址是指在栈中存在局部变量.上一级函数返回地址.寄 ...

  8. LeetCode 10——正则表达式匹配

    1. 题目 2. 解答 在 回溯算法 中我们介绍了一种递归的思路来求解这个问题. 此外,这个问题也可以用动态规划的思路来解决.我们定义状态 \(P[i][j]\) 为子串 \(s[0, i)\) 和 ...

  9. 多进程---multiprocessing/threading/

    一.多进程:multiprocessing模块 多用于处理CPU密集型任务 多线程 多用于IO密集型任务 Input Ouput 举例: import multiprocessing,threadin ...

  10. layui框架中layer父子页面交互的方法分析

    本文实例讲述了layui框架中layer父子页面交互的方法.分享给大家供大家参考,具体如下: layer是一款近年来备受青睐的web弹层组件,官网地址是:http://layer.layui.com/ ...