魔力大帅 發表於 2019-8-18 16:15:00

React与Typescript整合

<h2 class="md-end-block md-heading md-focus">&nbsp;<span class="md-plain">0. Typescript</span></h2>
<p class="md-end-block md-p"><span class="md-tab">   <span class="md-plain">Typescript对于前端来说可以说是越来越重要了,前端的很多项目都用Typescript进行了重构。这主要得益于Typescript有比较好的类型支持,在编码的过程中可以很好地做一些类型推断(主要是编辑器会有代码提示,就很舒服)。再者Typescript的语法相较于javascript更加严谨,有更好的ES6的支持,这些特性使得使用ts编码更加高效,尽量避免javascript中容易造成模糊的坑点。<span class="md-softbreak"> <span class="md-tab"> <span class="md-plain">我最近也正在学Typescript的一些知识,无奈本人实习所在的公司貌似暂时还不打算使用typescript,无奈只好自己琢磨,尝试将typescript与react进行整合,花了挺长时间的,关键在于typescript中需要对变量进行约束。</span></span></span></span></span></p>
<h2 class="md-end-block md-heading"><span class="md-plain">1. react的typescript版本项目初始化</span></h2>
<p class="md-end-block md-p"><span class="md-tab">   <span class="md-plain">这个没有好说的,使用react脚手架就可以初始化react项目,默认情况下安装的是javascript版本的项目,在脚手架命令中加上typescript的配置参数,就可以初始化typescript版本的react项目啦。</span></span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">create-react-app react-todo-ts --typescript</pre>
</div>
<h2 class="md-end-block md-heading"><span class="md-plain">2. react-todo-ts</span></h2>
<p class="md-end-block md-p"><span class="md-tab">   <span class="md-plain">本次主要通过一个简单的Todo应用,对于在React中整合typescript的流程有一个简单的认识。我采用的目录结构比较简单(ps:按照常理,一个简单的Todo应用其实没有必要整的这么复杂,也没有必要使用redux增加项目的复杂度,不过此处只是做一个演示,而在redux中也是需要使用typescript的类型声明的,否则可能无法通过编译)’</span></span></p>
<p class="md-end-block md-p"><span class="md-tab"><span class="md-plain"><span class="md-softbreak"><span class="md-tab"><span class="md-plain">目录结构如下:<span class="md-softbreak"><br></span></span></span></span></span></span></p>
<p class="md-end-block md-p"><span class="md-tab"><span class="md-plain"><span class="md-softbreak"><span class="md-tab"><span class="md-plain"><img src="https://img2018.cnblogs.com/blog/1570047/201908/1570047-20190818155933647-533616401.png" alt=""></span></span></span></span></span></p>
<p class="md-end-block md-p"><span class="md-plain">做个简单的说明:</span></p>
<ul class="ul-list" data-mark="-">
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">components中主要存放组件</span></p>






</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">store中包含了一些redux相关的代码</span></p>






</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">types只用存放公用的类型定义以及接口</span></p>






</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">index.tsx是项目默认的入口文件</span></p>






</li>






</ul>
<p class="md-end-block md-p"><span class="md-plain">package.json文件的说明:</span></p>
<p class="md-end-block md-p"><span class="md-tab"> <span class="md-plain">其中有几个声明文件是不需要的:@types/antd,@types/redux-thunk这两个声明文件是不需要的,它们的类型声明文件在antd和redux-thunk库中已经存在。(另外,本来想使用redux-thunk模拟一下异步请求的,但在整合的过程中稍微还有点问题,因此此版本并没有异步action的整合)。</span></span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">{
"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": [
      "&gt;0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
}
}
​</pre>
</div>
<p class="md-end-block md-p"><span class="md-tab"> <span class="md-plain">组件拆分说明:</span></span></p>
<p class="md-end-block md-p"><span class="md-tab"><span class="md-plain"><img style="display: block; margin-left: auto; margin-right: auto" src="https://img2018.cnblogs.com/blog/1570047/201908/1570047-20190818160056495-883478724.png" alt="" width="729" height="433"></span></span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="G:%5CDoc%5Cmarkdown%5C%E5%89%8D%E7%AB%AF%E7%AC%94%E8%AE%B0%5Cblog%5Cassets%5CSnipaste_2019-08-18_14-33-00.png"><img alt="" data-alt="Snipaste_2019-08-18_14-33-00" data-local-refresh="true"></span></p>
<h2 class="md-end-block md-heading"><span class="md-plain">3.typescript与antd整合</span></h2>
<p class="md-end-block md-p"><span class="md-plain">此处选取Header组件的代码做说明</span></p>
<ol class="ol-list">
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">Component类的变化</span></p>
<p class="md-end-block md-p"><span class="md-plain">首先,变化的是Component类,我们可以通过泛型的方式约束组件的state和props的类型</span></p>
</li>
</ol>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">interface IHeaderProps {
todoList:ITodo[];
addTodoAction: typeof addTodoAction
}

interface IHeaderState {
todoText:string;
}

class Header extends Component&lt;IHeaderProps, IHeaderState&gt; {
state = {
    todoText: ''
}
    ...
    ...
render() {
    return (
      &lt;Row&gt;
      &lt;Col span={16}&gt;
          &lt;Input placeholder="please input todo:" value={this.state.todoText} onChange={(e) =&gt; this.handleChange(e)} onKeyDown={(e) =&gt; this.handleKeyDown(e)}&gt;&lt;/Input&gt;
      &lt;/Col&gt;
      &lt;Col span={8}&gt;
          &lt;Button disabled={this.state.todoText.trim() === ''} type={'primary'} style={{ marginLeft: '50%', transform: 'translateX(-50%)' }} onClick={() =&gt; this.handleAdd()}&gt;添加&lt;/Button&gt;
      &lt;/Col&gt;
      &lt;/Row&gt;
    )
}
}</pre>
</div>
<p class="md-end-block md-p"><span class="md-plain">此处通过Component&lt;IHeaderProps, IHeaderState&gt;约束Header组件中的props和state属性,这样做以后,Header中的props属性必须满足IHeaderProps接口,state必须满足IHeaderState接口</span></p>
<ol class="ol-list" start="2">
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">事件交互部分代码的变化</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">handleChange = (e:ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    const { value } = e.currentTarget;
    this.setState({ todoText: value });
}

handleAdd = () =&gt; {
    const { todoText } = this.state;
    if(todoText.trim() === '') {
      return;
    }
    this.props.addTodoAction({
      content: todoText,
      done: false
    });
    this.setState({ todoText: '' })
}

handleKeyDown = (e:KeyboardEvent&lt;HTMLInputElement&gt;) =&gt; {
    if(e.keyCode === 13) {
      console.log(e.keyCode);
      this.handleAdd();
    }
}

   render() {
    return (
      &lt;Row&gt;
      &lt;Col span={16}&gt;
          &lt;Input placeholder="please input todo:" value={this.state.todoText} onChange={(e) =&gt; this.handleChange(e)} onKeyDown={(e) =&gt; this.handleKeyDown(e)}&gt;&lt;/Input&gt;
      &lt;/Col&gt;
      &lt;Col span={8}&gt;
          &lt;Button disabled={this.state.todoText.trim() === ''} type={'primary'} style={{ marginLeft: '50%', transform: 'translateX(-50%)' }} onClick={() =&gt; this.handleAdd()}&gt;添加&lt;/Button&gt;
      &lt;/Col&gt;
      &lt;/Row&gt;
    )
}</pre>
</div>
<p class="md-end-block md-p"><span class="md-plain">在ts中我们定义一个函数时必须要指定函数参数的类型,当我们在定义handler函数时,需要用到event对象时,我们又该如何声明event对象的类型呢?</span></p>
<p class="md-end-block md-p"><span class="md-plain">最开始的时候,我一般为了避免报错,不管三七二十一就是一个any声明,但这样其实就失去了类型推断的意义。</span></p>
<p class="md-end-block md-p"><span class="md-plain">在本项目中react的事件类型分为两类:</span></p>
<ol class="ol-list">
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">antd组件上的事件类型</span></p>
<p class="md-end-block md-p"><span class="md-plain">antd组件中的事件类型一般在antd的库中都会定义,但是有些组件与原生的事件定义类型一致</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">原生组件上的事件类型</span></p>
<p class="md-end-block md-p"><span class="md-plain">原生组件的事件类型一般定义在@types/react库中,可以从react库中引入事件类型,一般原生事件类型的命名方式是通过(事件名称&lt;元素类型&gt;)的方式来声明的</span></p>
<p class="md-end-block md-p"><span class="md-image md-img-loaded" data-src="G:%5CDoc%5Cmarkdown%5C%E5%89%8D%E7%AB%AF%E7%AC%94%E8%AE%B0%5Cblog%5Cassets%5C1566112114142.png"><img alt="" data-alt="1566112114142" data-local-refresh="true"></span></p>
</li>
</ol></li>
</ol>
<p class="md-end-block md-p"><span class="md-plain">   在vscode下,当你不确定事件类型的时候,hover上去会有函数签名提示,就可以比较方便地确定事件类型了<span class="md-softbreak">&nbsp;</span></span></p>
<p class="md-end-block md-p"><span class="md-plain"><span class="md-softbreak"><img style="display: block; margin-left: auto; margin-right: auto" src="https://img2018.cnblogs.com/blog/1570047/201908/1570047-20190818160418089-394231033.png" alt="" width="764" height="526"><br></span></span></p>
<h2 class="md-end-block md-heading"><span class="md-plain">4. typescript与redux整合</span></h2>
<p class="md-end-block md-p"><span class="md-plain">主要针对todoList的操作进行</span></p>
<ol class="ol-list">
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">对于todo的结构定义一个接口</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">export interface ITodo {
content:String;
done:boolean;
}</pre>
</div>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">确定对todoList的操作(添加todo,删除todo,修改完成状态),然后定义相关的action</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">import { ADD_TODO, DELETE_TODO, CHANGE_TODO_STATUS } from './action-types';

import { ITodo } from '../types';

export const addTodoAction = (todo:ITodo):AddTodoAction =&gt; ({ type: ADD_TODO, todo });
export const deleteTodoAction = (index:number):DeleteTodoAction =&gt; ({ type: DELETE_TODO, index });
export const changeTodoStatusAction = (index:number):ChangeTodoStatusAction =&gt; ({ 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;
}</pre>
</div>
</li>
</ol><ol class="ol-list" start="3">
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">定义todoReducer,传入todoReducer的action有三种可能,从actions.ts中将action的类型导入</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">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) =&gt; {
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) =&gt; index !== (action as DeleteTodoAction).index);
    case CHANGE_TODO_STATUS:
      const nextTodo:ITodo[] = [...todos];
      let target:ITodo = nextTodo.find((todo, index) =&gt; index === (action as ChangeTodoStatusAction).index) as ITodo;
      target.done = !target.done;
      return nextTodo;
    default:
      return todos;
}
}
</pre>
</div>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">store中暴露store工厂函数,获取store类型的时候可以通过ReturnType获取</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">import { todoReducer } from './reducers';
import { combineReducers, createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';

const rootReducer = combineReducers({
todoList: todoReducer
})

export type RootState = ReturnType&lt;typeof rootReducer&gt;
// 向外暴露store工厂
export function configStore() {
return createStore(
    rootReducer,
    applyMiddleware(thunk)
);
}</pre>
</div>
</li>
</ol>
<h2 class="md-end-block md-heading"><span class="md-plain">5. react-redux整合</span></h2>
<p class="md-end-block md-p"><span class="md-tab"> <span class="md-plain">通过react-redux分离依赖的方式与javascript版本没有太大的区别|</span></span></p>
<ol class="ol-list">
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">使用provider高阶组件包裹App组件</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">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 = () =&gt; {
return (
    &lt;Provider store={store}&gt;
      &lt;App/&gt;
    &lt;/Provider&gt;
)
}

ReactDom.render(
(
    &lt;Root/&gt;
),
document.querySelector('#root')
);
​</pre>
</div>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">内部组件引入,主要的不同点在于引入时需要将RootState的类型一同引入,在定义mapStateToProps函数时需要定义参数的类型。</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">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&lt;IListProp&gt; {

handleChange = (index:number) =&gt;{
    this.props.changeTodoStatusAction(index);
}

handleDelete = async (index:number) =&gt; {
    await this.props.deleteTodoAction(index);
    message.success("删除成功", 0.5);
}

render() {
    const { todoList } = this.props;
    return (
      &lt;div&gt;
      {
      todoList.length ? (
          &lt;div&gt;
            {
            todoList.map((todo, index) =&gt; (
               &lt;Row key={index}&gt;
               &lt;label&gt;
                  &lt;Col span={1}&gt;
                      &lt;Checkbox checked={todo.done} onChange={() =&gt; { this.handleChange(index) }}&gt;&lt;/Checkbox&gt;
                  &lt;/Col&gt;
                  &lt;Col span={20}&gt;
                      &lt;span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}&gt;
                        {
                        todo.content
                        }
                      &lt;/span&gt;
                  &lt;/Col&gt;
                  &lt;Col span={3} style={{marginTop: '10px'}}&gt;
                      &lt;Button type={'danger'} size={'small'} onClick={() =&gt; {this.handleDelete(index)}}&gt;删除&lt;/Button&gt;
                  &lt;/Col&gt;
               &lt;/label&gt;
               &lt;/Row&gt;
            ))
            }
          &lt;/div&gt;
      )
      :
      (&lt;Empty/&gt;)
      }
      &lt;/div&gt;
    )
}
}

const mapStateToProps = (state:RootState) =&gt; ({
todoList: state.todoList,
})

export default connect(
mapStateToProps,
{
    deleteTodoAction,
    changeTodoStatusAction
}
)(List);
​</pre>
</div>
</li>
</ol>
<h2 class="md-end-block md-heading"><span class="md-plain">6. 异步action</span></h2>
<p class="md-end-block md-p md-focus"><span class="md-plain md-expand">redux本身并不支持异步action,可是在使用的时候往往是需要发送异步请求的。在整合的过程中,存在一些问题,在View层通过事件发送一个异步action后,如何获得对应的promise状态,然后根据promise的状态做出相应的响应,可能还需要再看一看。</span></p>
<p class="md-end-block md-p md-focus"><span class="md-plain md-expand">---------------------------------------------------------------------------------------</span></p>
<p class="md-end-block md-p"><span class="md-plain md-expand">项目源码请戳--&gt;&nbsp;https://github.com/zhangzhengsmiling/React-Todo-typescript.git</span></p>
<p class="md-end-block md-p"><span class="md-plain md-expand">---------------------------------------------------------------------------------------</span></p>

</div>
<div id="MySignature" role="contentinfo">
    我不去想是否能够成功,既然选择了远方,便只顾风雨兼程。<br><br>
来源:https://www.cnblogs.com/zhangzhengsmiling/p/react-todo-typescript.html
頁: [1]
查看完整版本: React与Typescript整合