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 MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...