Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2020-04-04] Immutable.js #92

Open
gracekrcx opened this issue Apr 4, 2020 · 4 comments
Open

[2020-04-04] Immutable.js #92

gracekrcx opened this issue Apr 4, 2020 · 4 comments

Comments

@gracekrcx
Copy link
Owner

gracekrcx commented Apr 4, 2020

Immutable

immutable 是指在創建變數、賦值後便不可改變,若對其有任何變更(例如:新增、修改、刪除),就要回傳一個新值。

export function foo() {
 const data = {key: 'value' }
 touchFn(data)
 console.log(data.key) // ???
}

// 如果是這樣
export function foo() {
 const data = Immutable.Map({key: 'value' })
 touchFn(data)
 console.log(data.get('key')) // value
}

對 arr1 進行修改產生 arr2, arr3

  1. 都沒有影響 arr1 的值
  2. 也都宣告不同的變數去接收新的回傳值
const arr1 = [1,2,3,4]
const arr2 = arr1.filter((item)=>item !== 3)
const arr3 = [...arr1, 5, 6]
console.log(arr1) // [1, 2, 3, 4]
console.log(arr2) // [1, 2, 4]
console.log(arr3) // [1, 2, 3, 4, 5, 6]

why use Immutable

當我們在 redux 或是 react 中使用 immutable 的寫法
其實最主要的目的,是要在資料有異動之後
產生新的記憶體位置
讓 react 判別需要 re-render

重點是 state
redux store 裡的數據,是不可改變的,不能直接對它做編輯
你能做的就只有把整個 state 都換掉
如果 newState === oldState 就不會 re-render
所以才要用 Object.assign({}, state, {age: 25}) 這種方法
而不是直接 state.age = 25; return state
因為後者 newState === oldState 會是 true

重點

沒有變動的地方,就直接使用;有變動的地方就需要 new 一份新的去更改值
--> 實做的時候,有幾種方法可以選擇

  • Immutable.js
  • ES7 的 object spread

Immutable.js

  1. 實作 immutablity 的 library
  2. 實現資料 Immutable,適合處理大數據量級的、複雜的資料結構化
  3. 使用 immutable js 的 API 來改變資料,都會收到回傳一個新的 immutable 資料
  4. Immutable.JS 做資料操作後,永遠會有一個新的記憶體位置產生,當你要改 a 節點時就是複製一份 a 的儲存路徑出來修改,其他沒有受影響的節點才 reference 舊的,所以如果你操作 3 次資料就會有 3 個不同的記憶體位置產生

structural sharing

利用結構共享(structural sharing)的方式實作 persistent(持久的)data structure
此篇文章解釋了效能優化與 persistent
Good Morning, Functional JS (Day 24, 使用 Immutable.js)

// 共享
let a = Immutable.Map({
  select: 'users',
  filter: { name: 'Cam' }
})

let b = a.set('select', 'people');

console.log('A',a === b) // false
console.log('B',a.get('filter') === b.get('filter'))  // true

Persistent Data Structure
使用 old data 創建 new data 後,要保證 old data 同時 『可用』且『不變』

support lazy operation(延遲優化)

img

Immutable.js api

Immutable 資料結構與原生 JS

  • 陣列 - List
  • 物件 - Map

fromJS()
主要功能就是將你丟給他的 JavaScript 物件或陣列轉換成對應的 Immutable.js 結構

immutable.fromJS([1,2,3,4,5])    // 原生 array  --> List
immutable.fromJS({name:'Min', age:17})   // 原生 object  --> Map

List.of()
listA 並沒有因為 push() 的關係改變了自已原本的結構

var listA  = Immutable.List.of('a','b','c')
var listB = listA.push('aa')
console.log('listA', listA.toJS()) // ["a", "b", "c"]
console.log('listB', listB.toJS()) // ["a", "b", "c", "aa"]

List 修改(只有一層)
set(index: number, value)

 var listChange = listA.set(1,5)
 console.log('listA', listChange.toJS())  // ["a", 5, "c"]

List 取值(只有一層)

console.log(listA.get(1)); // b

List 內部有包其他 List 時,則需要使用 setIn() 與 getIn()

var listA = Immutable.fromJS([1,[2,3],4])
var listB = listA.setIn([1,1],'bbbb')
console.log(listB.toJS())  // [1, [2, "bbbb"], 4]
// [1,1] 其實就是要取得 [1,[2,3],4] 裡的 [2,3] 裡的 3 並將它修改成 "bbbb"。

// 取值
console.log(listB.getIn([0]))  // 1
console.log(listB.getIn([1,0]))  // 2
console.log(listB.getIn([1,1]))  // bbbb
console.log(listB.getIn([2]))  // 4

// 陣列裡的數字是每個層級的 Index

Map

var mapA = Immutable.Map({a:1,b:2})
var mapB = mapA.set('ccc','111')

console.log(mapA.get('a'))  // 1
console.log(mapB.get('ccc'))  // 111

深度取值與改值也是使用 setIn()getIn() :

var mapA = Immutable.fromJS({
 name: 'Vivi',
 class: {
  id: 12,
  color: 'blue',
  location: '303A'
 }
})


var mapB = mapA.setIn(['class','location'], '101B')
console.log(mapA.getIn(['name']))
console.log(mapA.getIn(['class','color']))
console.log(mapB.getIn(['class','location']))

總結:

  1. JS array method,在 Immutable.js 都有實作,可無縫接軌的使用
  2. fromJS()做的轉換是深轉換 (Deep Conversion)
  3. map.get('key') 而不是 map.key,array.get(0) 而不是 array[0]

更多

Immutable.is();
Value equality check

let map1 = Immutable.Map({a:1, b:1, c:1});
let map2 = Immutable.Map({a:1, b:1, c:1});
map1 === map2;  // false

Immutable.is(map1, map2);  // true

你需要 immutable.js 嗎?

你需要 immutable.js 嗎?

  1. 你在 redux 專案裡用到的 immutable.js api 是不是用原生的 js 或是偶而搭配一下 Deep Clone 就可以達成?
  2. 你在處理的是一筆資料,還是一千筆資料?

寫 React 的時候常常聽到 immutable,什麼是 immutable ?

同場加映 Immer

Immutability is Changing: From Immutable.js to Immer - ForwardJS 2019
img

參考文章

Immutable 详解及 React 中实践
Immutable.js 簡介
Immutable.js, persistent data structures and structural sharing
React.js Conf 2015 - Immutable Data and React
Immutability 為何重要?淺談 immutable.js 和 seamless-immutable
深入探究Immutable.js的实现机制(一) 這篇在解釋原理

@gracekrcx
Copy link
Owner Author

const state = {
  name: 'Vivi',
  age: 18,
}
// 使用相同的 age : 18 去 Shallow-cloning
const a = {...state, age:18}

state.name === a.name // true
state.age === a.age // true

@gracekrcx
Copy link
Owner Author

沒有 Immutable 的概念前

const props = {
  id: 1,
  list: [1, 2, 3]
}

const list = props.list;
list.push(4)
const nextProps = {
  ...props,
  list // 放入舊的 list
}

props.list === nextProps.list // true 

有了 Immutable 的概念

const props = {
  id: 1,
  list: [1, 2, 3]
}

const nextProps = {
 ...props,
 list:[...props.list, 4]  // 做一次 spread operator
}

props.list === nextProps.list // false

@gracekrcx
Copy link
Owner Author

gracekrcx commented Apr 11, 2020

改 points 的值,也是必須從最上層一直複製下去到 points 才能做更改

const state = {
 school: {
  name: "school",
  house: {
   name: "room1",
   points: {
    vip:225
   }
  },
  open:'am7:00'
 }
}
const newState = {
 ...state, 
 school:{
  ...state.school, 
  house:{
   ...state.school.house, points: {
    vip:450
   } 
  }
 }
}
state.school.house.points  // {vip: 225}
newState.school.house.points  // {vip: 450}

@gracekrcx
Copy link
Owner Author

const state = {
 school: {
  name: "school",
  house: {id:'room1'}
 },
 open:{am:'7:00'}
}
const newState = {
 ...state, school:{
  ...state.school, house:{
   id:333
  }
 }
}
newState.open === state.open // true
// 第一層的 open ,修改後還是共用記憶體位置
// 對應 spread operator 複製第一層
// 此結果合理

state.school === newState.school // false
// 第一層的 school 在一開始就被給予了一個新的 object 所以記憶體位置就換了

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant