概要
在專案中經常會遇到一些問題,比如把一個物件賦值給另外一個物件後,如何實現修改被賦值的物件而原物件不變;一般做法是copy,而copy又分為shallowCopy(淺拷貝)和 deepCopy(深拷貝);shallowCopy對於層級比較深的物件來說然並*用,那麼只能用deepCopy來避免上面遇到的情況,但deepCopy是一個很耗效能的操作。再比如使用react技術棧來做專案時,經常需要我們手工去判斷某些元件是否需要重新渲染來減少我們可愛又傲嬌的DOM操作,react提供shouldComponentUpdate鉤子函式來判斷該元件樹是否需要重新渲染,在該鉤子函式中一般使用淺比較和深比較來判斷該元件樹是否需要重新渲染;如果只是淺比較,只需要PureRender就能滿足,那麼需要深比較呢?
上面瞎逼逼一堆廢話無非是想說用immutable的好處,它能解決這些問題呀,它的共享結構多牛比呀,零學習成本讓你開開心心玩時間旅行。
什麼是immutable
immutable聽說是Facebook 工程師 Lee Byron 花費 3 年時間打造,與 React 同期出現,但沒有被預設放到 React 工具集裡(React 提供了簡化的 Helper);immutable一旦被建立終身不會變化,每次增加或修改還是刪除都是返回一個新的物件;immutable內部採用Structure Sharing(結構共享)來避免每一次deepCopy的效能損耗,每一次增刪改都只會改變當前節點和父節點並返回一個新物件。
聽說 Immutable 可以給 React 應用帶來數十倍的提升,個人感覺10應該達不到,但是確實提升不少效率和避免因物件被修改而導致的錯誤,雖然可以在專案中使用Object.assign或者lodash來避免原物件被修改而導致的一些錯誤,但是這兩貨也有很多缺點,Object.assign只能深拷貝一個層級,而且你會發現滿屏的Object.assign,對於處女座的作者肯定是不能容忍此事的發生;Immutable提供了is方法,因為Immutable內部使用的是Trie 資料結構,所以只需要比較hashCode或valueOf來避免深度比較。
下面介紹一下immutable中常用的map和list的一些基礎用法,當然還有很多其他型別( Collection、Set、Record、Seq等),本文介紹一些常用API讓讀者瞭解使用immutable基本是零學習成本,當然你也可以選擇功能更單一的seamless-immutable,程式碼庫非常小,壓縮後下載只有 2K:
-
將js轉換成immutable物件:
- immutable.fromJs(obj) //將js物件或陣列深拷貝到immutable物件
- immutable.Map(obj)//將js物件深拷貝為immutable物件
- immutable.List(arr) //將js陣列深拷貝為immutable陣列
-
immutable.List
- Immutable.List([1,2])//初始化一個list物件
- immutableA.size //求長度
- Immutable.List.isList //判斷是否陣列
- immutableData.get //取值
- immutableA.set //設值
- ….其他操作
-
immutable.Map
- Immutable.Map({a: 1})//初始化一個map物件
- Immutable.Map.isMap //判斷是否物件
- immutableData.get //取值
- immutableA.set //設值
- ….其他操作
react+redux+immutable開發流程
在react中使用immutable時應該注意避免出現toJS操作,因為toJS和fromJS是把整個資料結構遍歷一遍,還要建立新物件來儲存值,是很耗效能的操作;建議專案中全部使用immutable,資料從服務請求回來轉化成immutable再給redux,只有在提交資料和使用第三方不支援immutable元件或者外掛時才轉化成原生物件;下面是使用immutable的開發流程。
在沒使用immutable之前react中的狀態操作
//操作物件之前拷貝
let newState = Object.assign({}, this.state.obj, {
`a`: `test`
});
//操作之前陣列的拷貝
let newArr = [...arr, ...arr1];
複製程式碼
在react中使用immutable後的操作
//使用immutable來初始化狀態
this.state = Map(obj);
//改變state的值
this.setState(this.state.set(`a`, `test`));
複製程式碼
當react中使用了redux來進行狀態管理時如何在專案中使用immutable,由於 Redux 中內建的 combineReducers和 reducer 中的 initialState 都為原生的 Object 物件,所以不能和 Immutable 原生搭配使用;但是可以通過重寫combineReducers或者直接使用redux-immutablejs,本文介紹如何使用redux-immutablejs。
初始化initialState
按照 Redux 的工作流,我們從建立 store 開始。Redux 的 createStore 可以傳遞多個引數,前兩個是: reducers 和 initialState
const initialState = immutable.Map({
list: [],
card: {
id: ``,
title: ``,
desc: ``
}
});
複製程式碼
reducer
在reducer裡把以前的Obejct.assagin操作全部去掉,轉換成immutable物件。
export default function list(state = initialState, action) {
let card;
switch (action.type) {
case types.EDIT_ENTRY:
let lists = state.get(`list`);
lists = lists.push(action.newData.get(`data`));
card = action.newData.get(`card`);
return state.set(`list`, lists).set(`card`, card);
case types.VALUE_CHANGE:
return state.set(`card`, action.card);
case types.ADD:
return state.set(`card`, action.card);
default:
return state;
}
}
複製程式碼
action
在action裡面的所有物件都是immutable,為了區分原生物件,請不要在有的地方使用js原生物件,有的地方使用immutable物件,這樣往往會適得其反,容易混淆和immutable和原生轉換時效能的消耗。
export function editData() {
let card = Map({
id: ``,
title: ``,
desc: ``
});
return { type: types.ADD, card };
}
複製程式碼
接入redux
如果你不傳遞 initialState,redux-immutable也會幫助你在 store 初始化的時候,通過每個子 reducer 的初始值來構建一個全域性 Map 作為全域性 state。當然,這要求你的每個子 reducer 的預設初始值是 immutable的。只需要引入redux-immutable就可以在redux裡面使用immutable了,
import {
combineReducers
} from `redux-immutable`;
import list from `./list/listRedux`;
const rootReducer = combineReducers({
list
});
export default rootReducer;
複製程式碼
mapStateToProps
function mapStateToProps(state) {
return {
listMain: state.get(`list`)
};
}
export default connect(mapStateToProps)(Main);
複製程式碼
總結
immutable不是所有的專案都適合使用,在業務簡單資料關聯性不復雜等條件下使用反而增加了專案的複雜度,通過上面的開發流程大家也應該看出了immutable侵入性還是槓槓的,所以如果是專案起初考慮上immutable是可以的;但是對於老專案中使用就的考慮一下得失了。