Immutable Libraries
for React
Simon Bächler, ti&m
Simon Bächler
ti&m
@simonbaechler
The Flux Pattern
Redux
Immutable state
Redux requires
• Immutable objects for state
• Pure functions for reducers and selectors.
With mutations
case ADD_VIDEO_TO_SERVICE:
state.profile.accounts[action.serviceId]
.videos[action.videoInfo.id] = action.videoInfo;
return state;
With Immer
case ADD_VIDEO_TO_SERVICE:
return produce(state, draft => {
draft.profile.accounts[action.serviceId]
.videos[action.videoInfo.id] = action.videoInfo;
});
Immer
• By Michel Weststrate (Released January 2018)
• Producer function: mutable copy of the state.
• Simple API: Only one function:
produce<S>(currentState: S, draft: (S) => void): S
• Partial application is possible (by providing a function as the
first argument).
• Auto Freezing in Dev mode.
• Requires a Proxy-Polyfill für IE11.
With Immutable.js
case ADD_VIDEO_TO_SERVICE:
return state.setIn(
['profile', 'accounts', action.serviceId, 'videos', action.videoInfo.id],
action.videoInfo
);
Immutable.js
• By Facebook
• Uses a tree data type: Well suited for very large states.
• Comes with its own API. e.g. getters for accessing properties.
• Many helper methods for the Collection type.
• Lazy sequences and Records
• Requires a browser extension for debugging.
• The main developer has left Facebook.
• Immutable structures should not be mixed with JS structures
• toJS() is an expensive operation.
Tries
• Prefix tree
• The path is the key
• The leaf is the value
Updating a value
• Change the value of
'tea'.
Updating a value
• A new tree is created
(clone the root node)
• Only the leaves in the
path are re-created
• The old tree is still
there as long as a
reference is pointing
at it.
https://medium.com/@dtinth/immutable-js-persistent-data-structures-and-structural-sharing-6d163fbd73d2
Code and
Performance
Adding nodes
state => {
for (let i = 0; i < 1000; i++) {
state = {
ids: [...state.ids, i],
map: {
...state.map,
[i]: {
a: 1,
b: "Some data here"
}
}
}
}
}
Adding nodes
state => {
for (let i = 0; i < 1000; i++) {
state = state.withMutations(draft => {
draft.update("ids", ids => ids.push(i))
draft.setIn(["map", i], Immutable.Map({
a: 1,
b: "Some data here"
}))
})
}
}
Adding nodes
state => {
for (let i = 0; i < 1000; i++) {
state = produce(state, draft => {
draft.ids.push(i)
draft.map[i] = {
a: 1,
b: "Some data here"
}
})
}
}
Update lists
0
40
80
120
160
JS ImmutableJS Immer
t(ms)
Update lists
0
300
600
900
1200
1500
JS ImmutableJS Immer Immer ES5
t(ms)
Update a tree (1.2M nodes)
Update a tree (1.2M nodes)
state => {
for (let i = 0; i < 1000; i++) {
k = getKey(i);
state = {
...state,
[k]: {
...state[k],
[k]: {
...state[k][k],
[k]: {
...state[k][k][k],
[k]: {
...state[k][k][k][k],
[k]: {
...state[k][k][k][k][k],
[k]: {
...state[k][k][k][k][k][k],
[k]: i
}
}
}
}
}
}
}
}
Update a tree (1.2M nodes)
state => {
for (let i = 0; i < 1000; i++) {
k = getKey(i);
state = state.setIn([k, k, k, k, k, k, k], i)
}
}
Update a tree (1.2M nodes)
state => {
for (let i = 0; i < 1000; i++) {
k = getKey(i);
state = produce(state, draft => {
draft[k][k][k][k][k][k][k] = i
})
}
}
Update a tree (1.2M nodes)
0
7.5
15
22.5
30
JS ImmutableJS Immer
t(ms)
Update a tree (1.2M nodes)
0
22.5
45
67.5
90
JS ImmutableJS Immer Immer ES5
t(ms)
Update a tree (1.2M nodes)
state => {
for (let i = 0; i < 1000; i++) {
selectId = i % childrenCount
state = {
...state,
children: state.children.map((n, j) => {
if (j !== selectId) {
return n
}
return {
...n,
children: n.children.map((n, j) => {
if (j !== selectId) {
return n
}
return {
...n,
children: n.children.map((n, j) => {
if (j !== selectId) {
return n
}
return {
...n,
children: n.children.map((n, j) => {
if (j !== selectId) {
return n
}
return {
...n,
children: n.children.map(
(n, j) => {
if (j !== selectId) {
return n
}
return {
...n,
children: n.children.map(
(n, j) => {
if (
j !==
selectId
) {
return n
}
return {
...n,
value:
n.value +
1
Update a tree (1.2M nodes)
state => {
for (let i = 0; i < 1000; i++) {
selectId = i % childrenCount
state = state.updateIn(
[
"children",
selectId,
"children",
selectId,
"children",
selectId,
"children",
selectId,
"children",
selectId,
"children",
selectId,
"value"
],
value => value + 1
)
}
}
Update a tree (1.2M nodes)
state => {
for (let i = 0; i < 1000; i++) {
selectId = i % childrenCount
state = produce(state, draft => {
draft
.children[selectId].children[selectId]
.children[selectId].children[selectId]
.children[selectId].children[selectId]
.value += 1
})
}
}
Update a tree (1.2M nodes)
0
5
10
15
20
25
30
35
40
JS ImmutableJS Immer
t(ms)
Update a tree (1.2M nodes)
0
30
60
90
120
150
JS ImmutableJS Immer Immer ES5
t(ms)
Summary
• Use a helper library for updating immutable state.
• Native JS is really fast on small and nested structures.
• Immutable.js shines on large, flat data structures but
comes with some downsides (API, overhead, deep
integration)
• Immer makes your code more readable and is about 2x
slower in ES6, but massively slower when transpiled to
ES5.

Immutable Libraries for React

  • 1.
  • 2.
  • 3.
  • 5.
  • 6.
    Redux requires • Immutableobjects for state • Pure functions for reducers and selectors.
  • 8.
  • 9.
    With Immer case ADD_VIDEO_TO_SERVICE: returnproduce(state, draft => { draft.profile.accounts[action.serviceId] .videos[action.videoInfo.id] = action.videoInfo; });
  • 10.
    Immer • By MichelWeststrate (Released January 2018) • Producer function: mutable copy of the state. • Simple API: Only one function: produce<S>(currentState: S, draft: (S) => void): S • Partial application is possible (by providing a function as the first argument). • Auto Freezing in Dev mode. • Requires a Proxy-Polyfill für IE11.
  • 12.
    With Immutable.js case ADD_VIDEO_TO_SERVICE: returnstate.setIn( ['profile', 'accounts', action.serviceId, 'videos', action.videoInfo.id], action.videoInfo );
  • 13.
    Immutable.js • By Facebook •Uses a tree data type: Well suited for very large states. • Comes with its own API. e.g. getters for accessing properties. • Many helper methods for the Collection type. • Lazy sequences and Records • Requires a browser extension for debugging. • The main developer has left Facebook. • Immutable structures should not be mixed with JS structures • toJS() is an expensive operation.
  • 14.
    Tries • Prefix tree •The path is the key • The leaf is the value
  • 15.
    Updating a value •Change the value of 'tea'.
  • 16.
    Updating a value •A new tree is created (clone the root node) • Only the leaves in the path are re-created • The old tree is still there as long as a reference is pointing at it.
  • 18.
  • 19.
  • 20.
    Adding nodes state =>{ for (let i = 0; i < 1000; i++) { state = { ids: [...state.ids, i], map: { ...state.map, [i]: { a: 1, b: "Some data here" } } } } }
  • 21.
    Adding nodes state =>{ for (let i = 0; i < 1000; i++) { state = state.withMutations(draft => { draft.update("ids", ids => ids.push(i)) draft.setIn(["map", i], Immutable.Map({ a: 1, b: "Some data here" })) }) } }
  • 22.
    Adding nodes state =>{ for (let i = 0; i < 1000; i++) { state = produce(state, draft => { draft.ids.push(i) draft.map[i] = { a: 1, b: "Some data here" } }) } }
  • 23.
  • 24.
  • 25.
    Update a tree(1.2M nodes)
  • 26.
    Update a tree(1.2M nodes) state => { for (let i = 0; i < 1000; i++) { k = getKey(i); state = { ...state, [k]: { ...state[k], [k]: { ...state[k][k], [k]: { ...state[k][k][k], [k]: { ...state[k][k][k][k], [k]: { ...state[k][k][k][k][k], [k]: { ...state[k][k][k][k][k][k], [k]: i } } } } } } } }
  • 27.
    Update a tree(1.2M nodes) state => { for (let i = 0; i < 1000; i++) { k = getKey(i); state = state.setIn([k, k, k, k, k, k, k], i) } }
  • 28.
    Update a tree(1.2M nodes) state => { for (let i = 0; i < 1000; i++) { k = getKey(i); state = produce(state, draft => { draft[k][k][k][k][k][k][k] = i }) } }
  • 29.
    Update a tree(1.2M nodes) 0 7.5 15 22.5 30 JS ImmutableJS Immer t(ms)
  • 30.
    Update a tree(1.2M nodes) 0 22.5 45 67.5 90 JS ImmutableJS Immer Immer ES5 t(ms)
  • 31.
    Update a tree(1.2M nodes)
  • 32.
    state => { for(let i = 0; i < 1000; i++) { selectId = i % childrenCount state = { ...state, children: state.children.map((n, j) => { if (j !== selectId) { return n } return { ...n, children: n.children.map((n, j) => { if (j !== selectId) { return n } return { ...n, children: n.children.map((n, j) => { if (j !== selectId) { return n } return { ...n, children: n.children.map((n, j) => { if (j !== selectId) { return n } return { ...n, children: n.children.map( (n, j) => { if (j !== selectId) { return n } return { ...n, children: n.children.map( (n, j) => { if ( j !== selectId ) { return n } return { ...n, value: n.value + 1
  • 33.
    Update a tree(1.2M nodes) state => { for (let i = 0; i < 1000; i++) { selectId = i % childrenCount state = state.updateIn( [ "children", selectId, "children", selectId, "children", selectId, "children", selectId, "children", selectId, "children", selectId, "value" ], value => value + 1 ) } }
  • 34.
    Update a tree(1.2M nodes) state => { for (let i = 0; i < 1000; i++) { selectId = i % childrenCount state = produce(state, draft => { draft .children[selectId].children[selectId] .children[selectId].children[selectId] .children[selectId].children[selectId] .value += 1 }) } }
  • 35.
    Update a tree(1.2M nodes) 0 5 10 15 20 25 30 35 40 JS ImmutableJS Immer t(ms)
  • 36.
    Update a tree(1.2M nodes) 0 30 60 90 120 150 JS ImmutableJS Immer Immer ES5 t(ms)
  • 37.
  • 38.
    • Use ahelper library for updating immutable state. • Native JS is really fast on small and nested structures. • Immutable.js shines on large, flat data structures but comes with some downsides (API, overhead, deep integration) • Immer makes your code more readable and is about 2x slower in ES6, but massively slower when transpiled to ES5.

Editor's Notes

  • #4 Recommended Architecture Pattern for React Apps
  • #8 Javascript objects are mutable. When you want to update a deeply nested item it gets ugly.
  • #9 Mutations are not allowed in Redux.
  • #10 Immer provides a function called produce, which creates a mutable copy of the state.
  • #13 Immutable provides the function setIn which takes an array of keys for the path (keys can be anything).
  • #18 Structure of an Immutable Map with 100'000 entries. During an update, only the yellow nodes are re-created. A native Javascript object would copy all properties.
  • #19 There are different implementations for the trees and nodes. Immutable.js choses the best one transparently depending on the structure of the tree.
  • #21 Native JS Code State has a list and a map. We add 1000 items to each.
  • #22 Immutable.js code
  • #23 Immer code
  • #24 106 3 160 1150
  • #25 106 3 160 1150
  • #26 The tree has 6 levels with 9 children per node. It is only made of Javascript objects.
  • #27 The tree is only made of objects. The native Javascript code.
  • #28 The immutable.js code.
  • #29 The immer code.
  • #30 4 3 7 90
  • #31 4 3 7 90
  • #32 The tree has 6 levels with 9 children per node. Every node has a 'children' property that contains an array of child nodes.
  • #33 Javascript code
  • #34 Immutable.js code. Immutable.js gives you updateIn.
  • #35 Immer code.
  • #36 2 3 20 120
  • #37 2 3 20 120