一、はじめに#
Immutable は、変更できないという意味で、コンピュータ上では、一度作成されると変更できないデータを指します。
Immutable
オブジェクトの変更や追加、削除の操作は、新しいImmutable
オブジェクトを返します。
Immutable
の実装原理は、Persistent Data Structure
(永続データ構造)です:
- データを保存するためのデータ構造を使用します。
- データが変更されると、新しいオブジェクトが返されますが、新しいオブジェクトはできるだけ以前のデータ構造を利用し、メモリの浪費を防ぎます。
つまり、古いデータを使用して新しいデータを作成する場合、古いデータが同時に使用可能であり、変更されないことを保証するために、すべてのノードをコピーすることなく、Immutable
はStructural Sharing
(構造共有)を使用します。
オブジェクトツリーのノードが変更された場合、このノードと影響を受ける親ノードのみを変更し、他のノードは共有されます。
以下の図のようになります:
二、使い方#
Immutable
オブジェクトを使用するための主要なライブラリはimmutable.js
です。
immutable.js は、どのフレームワークにも基づいているかに関係なく、完全に独立したライブラリです。
これは、JavaScript に不変なデータ構造がないという問題を補うために登場し、パフォーマンスの問題を解決するために構造共有を使用しています。
内部では、完全な永続データ構造と、Collection
、List
、Map
、Set
、Record
、Seq
などの使用しやすいデータ型が提供されています。具体的には:
-
List:順序付きのインデックスセットで、JavaScript の Array に似ています。
-
Map:順序のないインデックスセットで、JavaScript の Object に似ています。
-
Set:重複のないセットです。
主なメソッドは次のとおりです:
- fromJS ():js データを Immutable タイプのデータに変換します。
const obj = Immutable.fromJS({a:'123',b:'234'})
- toJS ():Immutable データを JS タイプのデータに変換します。
- is ():2 つのオブジェクトを比較します。
import { Map, is } from 'immutable'
const map1 = Map({ a: 1, b: 1, c: 1 })
const map2 = Map({ a: 1, b: 1, c: 1 })
map1 === map2 //false
Object.is(map1, map2) // false
is(map1, map2) // true
-
get (key):データまたはオブジェクトの値を取得します。
-
getIn ([]):ネストされたオブジェクトまたは配列の値を取得します。引数は配列で、位置を示します。
let abs = Immutable.fromJS({a: {b:2}});
abs.getIn(['a', 'b']) // 2
abs.getIn(['a', 'c']) // 子ノードに値がない
let arr = Immutable.fromJS([1 ,2, 3, {a: 5}]);
arr.getIn([3, 'a']); // 5
arr.getIn([3, 'c']); // 子ノードに値がない
以下の例では、次のように使用します:
import Immutable from 'immutable';
foo = Immutable.fromJS({a: {b: 1}});
bar = foo.setIn(['a', 'b'], 2); // setInを使用して値を設定
console.log(foo.getIn(['a', 'b'])); // getInを使用して値を取得し、1を出力
console.log(foo === bar); // falseを出力
元のjs
に変更する場合は、次のようになります:
let foo = {a: {b: 1}};
let bar = foo;
bar.a.b = 2;
console.log(foo.a.b); // 2を出力
console.log(foo === bar); // trueを出力
三、React での使用#
Immutable
を使用すると、React
アプリケーションのパフォーマンスを向上させることができます。主にレンダリング回数の削減に表れます。
React
のパフォーマンスを最適化する際に、重複したレンダリングを避けるために、shouldComponentUpdate()
で比較を行い、true
を返すとrender
メソッドが実行されます。
Immutable
は、比較を行うために深い比較の方法を使用する必要がなく、is
メソッドを使用して比較を行うことができます。
また、redux
を使用する際にもImmutable
を組み合わせることができ、Immutable
を使用しない場合、データを変更するために深いコピーが必要です。
import '_' from 'lodash';
const Component = React.createClass({
getInitialState() {
return {
data: { times: 0 }
}
},
handleAdd() {
let data = _.cloneDeep(this.state.data);
data.times = data.times + 1;
this.setState({ data: data });
}
}
Immutable を使用した場合:
getInitialState() {
return {
data: Map({ times: 0 })
}
},
handleAdd() {
this.setState({ data: this.state.data.update('times', v => v + 1) });
// この時点ではtimesは変更されません
console.log(this.state.data.get('times'));
}
同様に、redux
でもデータをfromJS
で処理することができます。
import * as constants from './constants'
import {fromJS} from 'immutable'
const defaultState = fromJS({ //データをimmutableデータに変換する
home:true,
focused:false,
mouseIn:false,
list:[],
page:1,
totalPage:1
})
export default(state=defaultState,action)=>{
switch(action.type){
case constants.SEARCH_FOCUS:
return state.set('focused',true) //immutableデータを変更する
case constants.CHANGE_HOME_ACTIVE:
return state.set('home',action.value)
case constants.SEARCH_BLUR:
return state.set('focused',false)
case constants.CHANGE_LIST:
// return state.set('list',action.data).set('totalPage',action.totalPage)
//mergeは効率がよく、複数のデータを一度に変更します
return state.merge({
list:action.data,
totalPage:action.totalPage
})
case constants.MOUSE_ENTER:
return state.set('mouseIn',true)
case constants.MOUSE_LEAVE:
return state.set('mouseIn',false)
case constants.CHANGE_PAGE:
return state.set('page',action.page)
default:
return state
}
}