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组件的代码做说明
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接口
事件交互部分代码的变化
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的事件类型分为两类:
antd组件上的事件类型
antd组件中的事件类型一般在antd的库中都会定义,但是有些组件与原生的事件定义类型一致
原生组件上的事件类型
原生组件的事件类型一般定义在@types/react库中,可以从react库中引入事件类型,一般原生事件类型的命名方式是通过(事件名称<元素类型>)的方式来声明的
在vscode下,当你不确定事件类型的时候,hover上去会有函数签名提示,就可以比较方便地确定事件类型了
4. typescript与redux整合
主要针对todoList的操作进行
对于todo的结构定义一个接口
export interface ITodo {
content:String;
done:boolean;
}确定对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;
}
定义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;
}
}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版本没有太大的区别|
使用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')
);
内部组件引入,主要的不同点在于引入时需要将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整合的更多相关文章
- 从零配置webpack(react+less+typescript+mobx)
本文目标 从零搭建出一套支持react+less+typescript+mobx的webpack配置 最简化webpack配置 首页要初始化yarn和安装webpack的依赖 yarn init -y ...
- React Hooks Typescript 开发的一款 H5 移动端 组件库
CP design 使用 React hooks Typescript 开发的一个 H5 移动端 组件库 English | 简体中文 badge button icon CP Design Mobi ...
- 转换 React 为TypeScript
转换 React 为TypeScript JavaScript import React from 'react'; import PropTypes from 'prop-types'; class ...
- 从零构建一个react+webpack+typescript的应用
今天要完成在windows下从零开始构建一个react应用的任务 首先,新建一个文件夹,然后在该文件夹下使用命令npm init 初始化一个node项目. 然后安装所需依赖, npm i react ...
- React Native 项目整合 CodePush 全然指南
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/y4x5M0nivSrJaY3X92c/article/details/81976844 作者 | 钱 ...
- Jest+Enzyme React js/typescript测试环境配置案例
本文案例github:https://github.com/axel10/react-jest-typescript-demo 配置jest的react测试环境时我们可以参考官方的配置教程: http ...
- 超简单的react和typescript和引入scss项目搭建流程
1.首先我们先创建一个react项目,react官网也有react项目搭建的命令 npx create-react-app my-app cd my-app 2.安装我们项目需要的样式依赖,这个项目我 ...
- Electron结合React和TypeScript进行开发
目录 结合React+TypeScript进行Electron开发 1. electron基本简介 为什么选择electron? 2. 快速上手 2.1 安装React(template为ts) 2. ...
- typescript整合到vue中的详细介绍,ts+vue一梭子
通过vue-cli命令行安装vue项目,注意不要eslint 安装依赖 cnpm install typescript --save-dev cnpm install ts-loader --save ...
随机推荐
- VS2019界面透明、主题修改和导出设置
目录 安装插件Color Theme Editor for Visual Studio 2019和ClaudiIDE 导入主题 背景修改 效果预览 导出设置遇到错误924 其他帮助文档 我自己用的主题 ...
- Internet History, Technology, and Security(week2)——History: The First Internet - NSFNet
前言: 上周学习了<电子计算机的曙光>,对战时及战后的计算机的历史发展有了更丰富的了解,今天继续coursera的课程,感觉已经有点适应了课程的节奏(除了经常有些奇奇怪怪的词汇看都看不懂@ ...
- 730KII 打印机 Win7 2017年11月更新系统补丁后无法打印
卸载11月份编号为KB4048960的系统更新
- Guava 已经学习的代码整理
Guava 已经学习的代码整理 Guava 依赖: compile group: 'com.google.guava', name: 'guava', version: '18.0' 以下是我自己在开 ...
- EventBus和Otto第三方构架
代码 添加依赖:implementation 'org.greenrobot:eventbus:3.0.0'1注册并声明订阅者,然后发布事件最后解除注册 @Override protected voi ...
- oracle触发器update本表数据
功能: 1. 允许/限制对表的修改 2. 自动生成派生列,比如自增字段 3. 强制数据一致性 4. 提供审计和日志记录 5. 防止无效的事务处理 6. 启用复杂的业务逻辑 开始 create trig ...
- jvm监测
jvm调优,首先,你得会jvm性能检测.开方得先诊断啊.
- centos7部署前后端分离项目的过程
概述 本文主要讲解在安装了centos7的Linux主机中部署前后端分离项目的过程. 前端项目名为:vue_project:后端项目名为:django_project. 将这两个项目放在/opt/wh ...
- PCIE手札
PCIE兼容了大部分PCI总线的特性,区别在于使用串行差分总线代替了并行总线,并实现了协议分层.PCIE的带宽与LANE数量和时钟频率相关,时钟频率支持2.5G和5G,Lane支持x1/x2/x4/x ...
- ajax传递json参数
var pros = []; for(var i = 1; i <= 2; i++) { var obj = {}; obj.id = i; obj.age = i*20; pros = pro ...