React與Typescript整合

来源:https://www.cnblogs.com/zhangzhengsmiling/archive/2019/08/18/react-todo-typescript.html
-Advertisement-
Play Games

0. Typescript Typescript對於前端來說可以說是越來越重要了,前端的很多項目都用Typescript進行了重構。這主要得益於Typescript有比較好的類型支持,在編碼的過程中可以很好地做一些類型推斷(主要是編輯器會有代碼提示,就很舒服)。再者Typescript的語法相較於j ...


 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

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 博客園美化首頁隨筆同時一天發佈內容分開分開版塊展示 一.直接上js代碼 二.缺點 1.編輯欄我把他刪了 因為我用不到 要修改區域在 ...
  • 深入Vue.js響應式原理 一、創建一個Vue應用 new Vue({ data() { return { name: 'yjh', }; }, router, store, render: h => h(App), }).$mount('#app'); 二、實例化一個Vue應用到底發生了什麼? v ...
  • 寫在前面: 該篇是個人平時對web前端小知識點的總結,會不定時更新...... 如有錯誤,敬請批評指正。 正文: 1、WWW是World Wide Web的縮寫。 2、HTML(Hyper Text Markup Language)是用於描述網頁文檔的超文檔標記性語言。 3、Web主要包括超文本傳輸 ...
  • jQuery知識梳理20190817 [TOC] 1. jQuery的特征 強大選擇器: 方便快速查找DOM元素 隱式遍歷(迭代): 一次操作多個元素 讀寫合一: 讀數據/寫數據用的是一個函數 鏈式調用: 可以通過.不斷調用jQuery對象的方法 事件處理 DOM操作(CUD) 樣式操作 動畫 瀏覽 ...
  • 隨機驗證碼: ...
  • module.exports Node應用由模塊組成,採用CommonJS模塊規範。根據這個規範,每個文件就是一個模塊,有自己的作用域。在這些文件裡面定義的變數、函數、類,都是私有的,對外不可見,因此規避掉了作用域污染。 根據CommonJS規定,每個模塊內部,module變數代表當前模塊,這個變數 ...
  • 一、查找字元串的位置(找到返回字元串首字母的位置,找不到返回-1): indexOf("string"):查找字元串string在字元串中首次出現的位置; indexOf("string",number):從number位置開始往後查找字元串string在字元串中首次出現的位置;number為負數從 ...
  • if(val.toString().length == 10){ val = val.toString().padEnd(13,"0")*1 //不夠十三位放後面補零,超過13位也可以 } toString() 方法可把一個邏輯值轉換為字元串,並返回結果。 ES2017 引入了字元串補全長度的功能。 ...
一周排行
    -Advertisement-
    Play Games
  • 基於.NET Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...