1. 程式人生 > >Redux進階(一)

Redux進階(一)

State的不可變化帶來的麻煩

在用Redux處理深度複雜的資料時會有一些麻煩。由於js的特性,我們知道當對一個物件進行復制時實際上是複製它的引用,除非你對這個物件進行深度複製。Redux要求你每次你返回的都是一個全新的State,而不是去修改它。這就要求我們要對原來的State進行深度複製。這往往帶來複雜的操作(查詢,合併)。一種簡單的情況是通過擴充套件符號或者Object.assign來處理:

return {
    ...state,
    data: {
        ...state.data,
        id: 5
    }
}

這種方式在處理簡單的資料來說是比較方便的,但是如果遇到更深一級的資料結構時會顯得很無力,而且程式碼會變得冗長。不僅僅如此,當我們要處理一個包含著物件的陣列時,我們要怎麼辦才好呢?我想,除了深度複製陣列然後修改新的陣列之外大概沒有其他的方法了吧?而且很重要的一點是,如果你對原來整個陣列進行了複製,那麼綁定了資料的UI會自動渲染,即使它們的資料沒有發生變化,簡單來說,就是你修改了某一條表格資料,但是介面上整個表格被重新渲染了:

const TablesSource = {
    query: 'tables',
    tableId: 10,
    data: [{
        key: 11,
        name: '胡彥斌',
        age: 32,//我要修改這裡,要複製整個陣列後修改新的嗎?
        address: '西湖區湖底公園1號'
    }, {
        key: 12,
        name: '胡彥祖',
        age: 42,
        address: '西湖區湖底公園1號'
    }]
};

Redux官方文件中提到了一種解決方案,即正規化化資料:概括起來就一句話:減少層級,唯一id索引,用後端建表的方法構建我們的資料結構。其中最重要原則無非是扁平化和關聯性。最終我們需要將資料形式轉化成以下格式:

{
  "entities": {
    "bykey": {
      "11": {
        "key": 11,
        "name": "胡彥斌",
        "age": 32,
        "address": "西湖區湖底公園1號"
      },
      "12": {
        "key": 12,
        "name": "胡彥祖",
        "age": 42,
        "address": "西湖區湖底公園1號"
      }
    },
    "table": {
      "10": {
        "query": "tables",
        "tableId": 10,
        "data": [
          11,
          12
        ]
      }
    }
  },
  "result": 10
}

按照滷煮的理解,正規化化資料無非就是給物件瘦瘦身,再深的層級,我們也儘量將它們扁平化,這樣會減少我們對State的查詢帶來的效能消耗。然後是建立索引表,標識每組資料之間的聯絡。那麼怎麼樣才能得到我們想要的資料呢?

normalizr方法使用指南

官方最薦normalizr模組,它的用法還是需要時間的去研究的。下面我們就以上面的資料為示例,說明它的用法:

$ npm i normalizr -S //下載模組

.........

import {normalize, schema} from 'normalizr';//日常匯入,沒問題
//原始資料
const TablesSource = {
    query: 'tables',
    tableId: 10,
    data: [{
        key: 11,
        name: '胡彥斌',
        age: 32,
        address: '西湖區湖底公園1號'
    }, {
        key: 12,
        name: '胡彥祖',
        age: 42,
        address: '西湖區湖底公園1號'
    }]
};

//建立實體,名稱為bykey,我們看到它的第二個引數是undefined,說明它是最後一層級的物件
const bykey = new schema.Entity('bykey', undefined, {
    idAttribute: 'key'
});
//建立實體,名字為table,索引是tableid。
const table = new schema.Entity('table', {
    data: [bykey] //這裡需要說明這些實體的關係,意思是bykey原來table下面的是一個數組,他對應的是data資料,bykey將會取這裡的資料建立一個以key為索引的物件。
}, {
    idAttribute: 'tableId'//以tableId為為索引
});

const normalizedData = normalize(TablesSource, table);//生成新資料結構

說明:new schema.Entity的第一個引數表示你建立的最外層的實體名稱,第二個引數是它和其他新建立的實體的關係,預設是最小的層級,即它只是最後一層,不包含其他層級了。第三個引數裡面有個idAttribute,指的是以哪個欄位為索引,預設是"id",它也可以是個引數,返回你自己構造的唯一值,記住,是唯一值。按照這樣的套路,你可以隨意構建你想要的扁平化資料結構,無論源資料的層級有多深。我們最終都會得到希望的資料結構。

{
  "entities": {
    "bykey": {、、實體名稱
      "11": {//我們之前設定的唯一所用key
        "key": 11,
        "name": "胡彥斌",
        "age": 32,
        "address": "西湖區湖底公園1號"
      },
      "12": {
        "key": 12,
        "name": "胡彥祖",
        "age": 42,
        "address": "西湖區湖底公園1號"
      }
    },
    "table": {//實體名
      "10": {、、唯一所用tableid
        "query": "tables",
        "tableId": 10,
        "data": [ //data變成了儲存key值索引的集合了!因為在之前我們說明了兩個實體之間的關係 data: [bykey]
          11,
          12
        ]
      }
    }
  },
  "result": 10//這裡同樣儲存著table實體裡面的索引集合 normalizr(TableSource, table)
}

github上有詳細的官方文件可供查詢,滷煮在此只是簡單的說明一下用法,諸位可以仔細觀察文件上的用法,不需要多少時間就可以熟練掌握。到此為止,萬里長城,終於走完了第一步。

如何將正規化化資料後再次轉化

什麼?好不容易轉化成自己想要的資料結構,還需要再次轉化嗎?很遺憾告訴你,是的。因為正規化化後的資料只便於我們在維護Redux,而介面業務渲染的資料結構往往跟我們處理後的資料是不一樣的,舉個栗子:bootstrap或者ant.design的表格渲染資料結構是這個樣的:

const dataSource = [{
  key: '1',
  name: '胡彥斌',
  age: 32,
  address: '西湖區湖底公園1號'
}, {
  key: '2',
  name: '胡彥祖',
  age: 42,
  address: '西湖區湖底公園1號'
}];

因而在介面引用State上的資料時,我們需要一箇中介,把正規化化的資料再次轉化成業務資料結構。我相信這個步驟十分簡單,只需要寫一個簡單的轉換器就行了:

const transform = (source) => {
    const data = source.entities.bykey;
    return Object.keys(data).map(v => data[v]);
};

const mapStateToProps = (state,ownProps) => ({table: transform(state)})

export default connect(mapStateToProps)(view)

如果你在mapStateToProps裡面斷點除錯,你會發現每一次dispatch都會強行執行mapStateProps方法保證物件的最新狀態(除非你引用的是基礎型別資料),因此,不管介面的操作是如何,被connect資料都會被強行執行一次,雖然介面沒有變化,但是顯然,js的效能會有折扣,尤其是對深度物件的複雜處理。因此,官方推薦我們建立可記憶的函式高效計算Redux Store裡面的衍生資料。

Reselect方法使用指南

//快取data裡面的索引
const reNormalDataSource = (state, props) => state.app.entities.table['10'].data;
//快取bykey裡面對得基礎資料
const reNormal = (state, props) => state.app.entities.bykey;
// 快取計算結果
const createNormalTableData = createSelector([reNormalDataSource, reNormal], (keys, source) => keys.map(item => source[item]));
//每次當mapStateToProps重新執行時,會儲存上次計算的結果,它只會重新計算變化的資料,其他非相關變化不做計算
const mapStateToProps = (state, own) => ({source: createNormalTableData(state)});

我在這裡做了個耍了點花樣,你可以看到,我是按照table.data這個陣列來查詢介面業務資料的。這種操作可以使得我們只需要關心table.data這個簡單的一維陣列,在刪除或者新增一條資料的時候顯得尤為有用。

我們最後為了計算state,引用了dot-prop-immutable模組,他是immutable的擴充套件,對於資料計算非常高效。我接著使用了另外一個dot-prop-immutable-chain模組,它增加了dot-prop-immutable的鏈式用法。關於dot-prop-immutable的用法滷煮不再詳細說明,在後面給出的例子中一眼就能看明白,而且官方文件上也有詳細說明。下面我們通過一個表格增刪查改來實際展示我們這次的解決方案。

import {normalize, schema} from 'normalizr';
import dotProp from 'dot-prop-immutable-chain';

const reducer = (state = normalizedData, action) => {
    switch(action.type) {
        //修改一條資料
        case 'EDITOR':
            return dotProp(state).set(`entities.bykey.${action.key}.age`, action.age).value();
        //新增一條資料
        case 'ADD':
            const newId = UID++;
           return dotProp(state).set(`entities.bykey.${newId}`, Object.assign({}, model, {key: newId}))//新增一條新資料
               .merge(`entities.table.10.data`, [newId]).value();//新資料的data中的引用
        //刪除一條資料   
        case 'DELETE':
            const index = state.entities.table['10'].data.indexOf(Number(action.key));
            //可以看到,由於我們介面資料是根據data裡面的項來決定的,因此我們只需要處理data這個簡單的一維陣列,而這顯然要容易維護得多
            return dotProp(state).delete(`entities.table.10.data.${index}`).value();
    }
    return state;
};

瞧,我們展示了整個reducer,相信它已經變得容易維護得多了,並且由於使用了正規化化資料結構以及immutable的擴充套件模組,不僅僅提升了計算效能,減少介面的的渲染,而且還符合ReduxState不可修改的原則。

結束語

React+Redux組合在實際應用過程中需要優化的地方還很多,這裡只是簡單展示其中的一個小點。雖然在計算dom介面變化時React已經做得足夠好,但並不意味著我們可以不用為介面渲染問題背鍋,React肩負了多數介面更新計算的任務,而讓前端開發人員更多地去處理資料,因此,我們可以在這裡層多花點時間把專案做好。

參考資料

相關推薦

Redux

State的不可變化帶來的麻煩 在用Redux處理深度複雜的資料時會有一些麻煩。由於js的特性,我們知道當對一個物件進行復制時實際上是複製它的引用,除非你對這個物件進行深度複製。Redux要求你每次你返回的都是一個全新的State,而不是去修改它。這就要求我們要對原來的State進行深度複製。這往往帶來複雜的

mysql mysql備份

mysql備份的目的: 實現災難恢復:誤操作、硬件故障、軟件故障、自然災害、黑客攻擊 註意的要點: 1、能夠容忍丟失多少數據 2、恢復數據所用的時間 3、備份需要的時間 4、是否對業務有影響 5、備份時服務器負載 備份類型 完全備份:備份整個

函數

並行 自己的 習題 文件 false 聲明 方式 關鍵字 true 1.命名空間 本質:存放名字與值的綁定關系 命名空間的分類:(1)全局命名空間(變量)->位於函數體外 (2)局部命名空間(變量)->

Redis高級

具體類 tro 類型 長度 刪除過期數據 專用 影響 生活 設置時間 一、redis中的事務 在關系型數據庫中事務是必不可少的一個核心功能,生活中也是處處可見,比如我們去銀行轉賬,首先需要將A賬戶的錢劃走,然後存到B賬戶上,這兩個步驟必須在同一事務中,要麽都執行,要麽都不執

SQL ServerT-SQL查詢和編程的背景

.com src 編程 server 分享 bubuko 進階 分享圖片 img SQL Server進階(一)T-SQL查詢和編程的背景

Redis高階

一、redis中的事務 在關係型資料庫中事務是必不可少的一個核心功能,生活中也是處處可見,比如我們去銀行轉賬,首先需要將A賬戶的錢划走,然後存到B賬戶上,這兩個步驟必須在同一事務中,要麼都執行,要麼都不執行,不然錢憑空消失了,換了誰也無法接受。 同樣,redis中也為我們提供了事務,原理是:先把一組同一事

SpringCloud從入門到——懂生活就懂微服務

避免 發現 官方文檔 隨著 並發 規範 只需要 組合 組件 內容   本文通過生活中的實際場景解釋單體應用和微服務應用的關系,以及SpringCloud中各組件的功能和含義。 適合人群   Java開發人員 說明   轉載請說明出處:SpringCloud從入門到進階(一)

Servlet第一個JSP頁面

前言       本章講解JSP的相關知識之JSP初步 方法 1.概念 在以前我們寫網頁的時候,都是用html進行書寫,這種網頁是靜態的,無法和伺服器進行一個互動,那麼怎麼辦呢?JSP技術應運而生,我們在web開發之中,Java和Jsp可以形成完美的

JavaScriptJS事件機制

前言       做了這麼久的鋪墊,終於迎來了新的篇章,該章介紹JS中的事件機制 方法 1.概念 我們知道,JS是參與網頁互動的一門指令碼語言,之前所說的都是JS的基本概念,那麼怎麼來進行互動呢!那就需要JS的事件機制來進行控制了,如按鈕的點選事件觸

Android: Launcher啟動過程

1.前言 最近一直在看 《Android進階解密》 的一本書,這本書編寫邏輯、流程都非常好,而且很容易看懂,非常推薦大家去看看(沒有收廣告費,單純覺得作者寫的很好)。 今天就將 Launcher 系統啟動過程 總結一下(基於Android 8.0 系統)。 文章

c語言指標

指標也是一種資料型別 指標是一個特殊的變數,它裡面儲存的數值被解釋成為記憶體裡的一個地址。要搞清一個指標需要搞清指標的四方面的內容:指標的型別,指標所指向的型別,指標的值或者叫指標所指向的記憶體區,還有指標本身所佔據的記憶體區。 1)指標是一種變數,佔有記憶體空間,用來儲存記憶體地址

C# Lambda 表示式

Lambda 表示式是一種可用於建立委託或表示式目錄樹型別的匿名函式。通過使用 lambda 表示式,可以寫入可作為引數傳遞或作為函式呼叫值返回的本地函式。 Lambda 表示式對於編寫 LINQ 查詢表示式特別有用。若要建立 Lambda 表示式,需要在 Lambda 運算

egg.js-基於koa2的node.js

一、路由進階Egg路由的路由重定向,路由分組 在router.js修改為如下格式require引用 module.exports = app => { const { router, controller } = app; require('./routers/admin')(app); re

python3.5-------------------網路通訊ip、埠,socket

1. ip地址:如198.168.1.1 ,用於在網路中標記區分每臺電腦,在本地區域網中是唯一的。window下程式->cmd->輸入ipconfig(檢視ipv4就是ip地址)。拓展:ip v4表示ip的第四個版本,目前出現了ipv6,ipv1/2/3/5都

PyQt5——讓視窗裝載更多的控制元件

1. QTabWidget的使用 視窗上側有標籤,選擇不同標籤進入不同佈局頁面 import sys from PyQt5.QtWidgets import * class TabDemo(QTabWidget): def __init__(self, parent=No

Android----MVP架構

相信大家一定在用mvp架構去設計App,但是在設計運用的過程中,大家有沒有考慮簡化程式碼,View層和Model層會有很多重複的程式碼,在顯示資料之前還需每次判斷View!=null,Presenter層每次需要去new Model層的例項,View層還有可能會對應多個Presenter

java:泛型

1、泛型簡介 所謂泛型,即通過引數化型別來實現在同一份程式碼上操作多種資料型別,泛型程式設計是一種程式設計正規化,他利用“引數化型別”將型別抽象化,從而實現更為靈活的複用。 先簡單給個例子: //可以想象這裡的T為Integer型別,以便於理解,其實它可以是任何型別 p

學習筆記之MongoDB

MongoDB的條件操作符 MongoDB中條件操作符有: (>) 大於 - $gt (<) 小於 - $lt (>=) 大於等於 - $gte (<= ) 小於等於 - $lte $gt -------- greater than

Kotlin系統入門與

一、什麼是Kotlin? Kotlin就是一門可以執行在Java虛擬機器、Android、瀏覽器上的靜態語言,他與Java 100%相容,如果對Java熟悉,可以發現Kotlin擁有自己

屬性動畫2:ValueAnimator高階

1. 概述 前面一篇屬性動畫1:基礎知識和ValueAnimator寫完,我對屬性動畫基礎知識和ValueAnimator的簡單用法有了一些瞭解。要想把屬性動畫吃透,我感覺需要更加深入的學習。現在,就從ValueAnimator的高階進階開始,繼續攻克