This is the 7th day of my participation in Gwen Challenge

What is immutable data?

Immutable data: Data that, once created, cannot be changed.

The implementation principle of Immutable Data is Persistent Data Structure, that is, when making changes, a new Immutable object is returned to ensure that the old Data is available and unchanged at the same time.

Immutable data adopts Structural Sharing (Structural Sharing), which can avoid the performance loss caused by copying all nodes, that is, only modify the changed node and the parent node affected by it, and other nodes are shared.

As shown in the figure above, to modify the Initial tree, we need to copy an identical tree first and then fuse the modified content (only the modified part and its path change, other parts remain unchanged) to generate the updated tree on the right. In this way, the reference address of the data before and after modification is different, so that we can judge whether the data has changed.

Why you need “immutable data”

The use of immutable data is fundamental to Redux because it:

  • Optimize performance: You don’t need to compare Store values to determine if references are equal
  • Easy to debug and test: Since the data before and after is different, you can store the data in an array and use it at any time.
  • Memory saving: By using structure reuse, memory can be reused as much as possible, and even previously used objects can be reused again. Objects that are not referenced are garbage collected.

How to manipulate immutable Data

Introduce three typical and different approaches, each with its own advantages, disadvantages and application scenarios.

Native ({… }, Object. The assign)

const state = { filter: "completed".todos: ["Learn React"]};// ...
constnewState1 = { ... state,todos: [...state.todos, "Learn Redux"]};// Object.assign
const newState2 = Object.assign({}, state, {
  todos: [...state, "Learn Redux"]});Copy the code

advantages

  • No additional installation libraries are required
  • There are no additional apis to learn
  • High performance

disadvantages

  • Encounter deeper level will be very headache, look at the person dazzling

    constnewData1 = { ... myData, {x: { ...myData.x, {
        y: {... myData.x.y, {z: 7}},a: { ...myData.a, { b: myData.a.b.concat(9)}}const newData2 = Object.assign({}, myData, {
      x: Object.assign({}, myData.x, {
        y: Object.assign({}, myData.x.y, { z: 7})}),a: Object.assign({}, myData.a, { b: myData.a.b.concat(9)})});Copy the code

Introduce object.assign

The object.assign () method is used to assign the values of all enumerable properties from one or more source objects to target objects.

Object.assign() requires special attention:

  • If the Object attribute value is a simple type (string, number), the new Object obtained by object.assign ({},srcobj) is a deep copy.

    // Simple type
    var obj1 = { name: "d" };
    var obj2 = { age: 23 };
    Object.assign(obj1, obj2);
    obj2.age = 29;
    console.log(obj1); // {name: "d", age: 23}
    Copy the code
  • If the attribute value is an object or some other reference type, it is actually shallow-copied for the object

    // Reference type
    var obj1 = { name: "d" };
    var obj2 = { age: { year: 2019}};Object.assign(obj1, obj2);
    obj1.age.year = 2020;
    console.log(obj2); // {name: "d", age: {year: 2020}}
    Copy the code
  • If the attribute value is an object or other reference type, the attributes of the object directly are not merged.

    var obj1 = { age: { year: 2020.mouth: 8}};var obj2 = { age: { year: 2019}};Object.assign(obj1, obj2);
    console.log(obj1); // { age: { year: 2019 } }
    Copy the code

Shallow copy of an object: A shallow copy is a memory address shared by objects, and changes in objects are reflected in each other. Deep copy of objects: The simple understanding of deep copy is that objects are placed in new memory and changes to the two objects do not affect each other.

Immutability-helper

Project address: github.com/kolodny/imm…

One day, immutability-Helper appeared in the React documentation, which could be considered a minimalist version of Immutable. Immutability-helper is more like Object. Assign syntax than Immutable. Js, but is therefore lighter and easier to use.

Take this example:

const newData = Object.assign({}, myData, {
  x: Object.assign({}, myData.x, {
    y: Object.assign({}, myData.x.y, { z: 7})}),a: Object.assign({}, myData.a, { b: myData.a.b.concat(9)})});Copy the code

Using immutability-Hepler we can write this

import update from "immutability-hepler";
const newData = update(myData, {
  x: { y: { z: { $set: 7}}},a: { b: { $push: [9]}}});Copy the code

advantages

  • Readability increased
  • Suitable for deeper nodes

disadvantages

  • Additional class libraries need to be introduced
  • Need to learn new grammar

A quick word about the API:

  • {$push: array} : array like push

    const initialArray = [1.2.3];
    const newArray = update(initialArray, { $push: [4]});// => [1, 2, 3, 4]
    Copy the code
  • {$unshift: array} : array like unshift

    const initialArray = [1.2.3];
    const newArray = update(initialArray, { $unshift: [4]});// => [4, 1, 2, 3]
    Copy the code
  • {$splice: array of arrays} : splice of arrays

    const collection = [1.2, { a: [12.17.15]}];const newCollection = update(collection, {
      2: { a: { $splice: [[1.1.13.14]]}}});// => [1, 2, {a: [12, 13, 14, 15]}]
    Copy the code

    Access key A of index 2 of collection and concatenate 1 item from index 1 while inserting 13 and 14 (delete 17)

  • {$set: any} : if there is a duplicate value, overwrite it and add it if there is no duplicate value

    const myData = { a: "1".b: "2" };
    const newData = update(myData, {
      a: { $set: "0" },
      c: { $set: "3"}});// => {a: "0", b: "2", c: "3"}
    Copy the code
  • {$merge: object} : Merges objects

    const myData = { a: "1".b: "2" };
    const newData = update(myData, {
      $merge: { a: "0".c: "3"}});// => { a: "0", b: "2", c: "3" };
    Copy the code
  • {$apply: function} : Perform a function operation based on the current value to obtain a new value

    const myData = [1.2.3.4.5];
    const newData = update(myData, {
      $apply: (val) = > val.reverse(),
    });
    // => [5, 4, 3, 2, 1]
    Copy the code

Immer (recommended)

Project address: github.com/immerjs/imm…

Immer is a new attempt at immutable by Mobx authors, author portals. Immer can handle immutable data in a more convenient way, optimizing efficiency based on a copy-on-write mechanism.

The basic workflow of Immer: All changes are applied to a temporary DraftState (as a proxy for CurrentState), and Immer generates NextState based on DraftState changes.

One of immer’s greatest strengths is its API for using native data structures. It is written as if it were modifying the data directly:

import produce from "immer";

const baseState = [
  {
    todo: "Learn typescript".done: true}, {todo: "Try immer".done: false,},];const nextState = produce(baseState, (draftState) = > {
  draftState.push({ todo: "Tweet about it" });
  draftState[1].done = true;
});
Copy the code

advantages

  • Very lightweight, compressed volume only 3KB
  • You don’t need to learn new apis, and it’s cheap to learn

performance

From official performance test data:

From the above observation, we can see that:

  • Immer (Proxy) is about two or three times slower than handwritten reducer, which can be ignored in practice
  • The operating efficiency of Immer is greatly affected by environmental factors. DefineProperty is used in the ES5 version of Immer, which is significantly slower to test. Try to use Immer in a proxy-enabled environment.

Performance optimization recommendations:

  • Prefrozen data

    Freeze (json) before adding large data to Produce. Immer will add new data faster because it will avoid recursive scanning and freezing new data.

  • Expensive search operations are performed in original state, not draft

  • Be flexible about whether to use Immer

    You can pair it with some pure Jacascript native operations, even inside Produce.

  • Improve produce as much as possible

    / / ❌
    for (let x of y) produce(base, (d) = > d.push(x));
    
    / / ✅
    produce(base, (d) = > {
      for (let x of y) d.push(x);
    });
    Copy the code

Redux series

  • Redux (1) : Three pieces (Action, Reducer, Store)
  • Redux (2) : Develop applications with React
  • Redux (3) : Middleware and Asynchronous Action
  • Redux (4) : How to organize “Action” and “Reducer” files?
  • Redux (5) : How can immutable data be used to improve performance?