Redux進階(一)

來源:互聯網
上載者:User
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的擴充模組,不僅僅提升了計算效能,減少介面的的渲染,而且還符合Redux的State不可修改的原則。

結束語

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

參考資料

 Redux中文文檔

把你的redux store 組織成資料庫形式

normalizr在GitHub上的地址

reselect在GitHub上的地址

dot-prop-immutable在GitHub上的地址

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.