Author: @wangly19, this article has been authorized to be exclusively used by the Nuggets Developer community public account, including but not limited to editing, marking original rights and other rights.

preface

MOBX is probably no stranger. Immer.js is also a masterpiece by the authors of this open source project, and is a practice for immutable management. And today, I’m going to do a simple little share about it.

Talk about why immer.js was chosen as an immutable solution, and some practical tips on the project.

What is immutable? Let me know

Advantages and disadvantages of IMmer

Here we summarize some advantages and disadvantages, in fact, the comparison is quite obvious, more practical than the current immutable.

advantages

  • Quick start, low cost of learning
  • Native syntax implementation with no additional dialect dependencies
  • Small volume, convenient and practical in tight space
  • API refining, easy to understand

disadvantages

  • Browsers need to supportproxyGrammar sugar, otherwise would be practicaldefinePropertyInstead of
  • The ES5 fallback implementation is about twice as fast as the proxy implementation, and in some cases worse.

In plain English, if you need to develop on IE10, don’t use it. Also highlighted here is a production speed for both apis. In 2021, there will be very few IE10 users.

Working mode

Immutable. Js, as you know, is implemented by maintaining its own set of data structures. Although it solved the problem, it also faced many problems.

Developers need to take the time to learn how to use data structures while juggling native types. For light users, it is undoubtedly very chicken ribs.

Immer, on the other hand, has no additional learning burden, is more suitable to the application scenario, and the transformation of the code is also very fast and elegant. The basic implementation idea is as shown in the figure below. The changes we make are only on the proxy of the current data. Once your changes are finished, new objects are generated based on the changes, making it very convenient to modify a data in the isolation sandbox without too many side effects.

How to use

In IMmer, the main operation is performed by the produce function, which takes two arguments.

produce(currentState, producer: (draftState) = > void): nextState
Copy the code
  • Current data:metadata
  • Draft function:The agent function

It’s also very simple to use. Here are some quick examples of how immer works in React.

Why React

Here, I’ll also talk about why immer is used. Previously, we also saw how React determines that view updates depend on shallow comparisons, as shown in the following example.

When we are basic data types, it doesn’t seem to be a problem, and data comparisons don’t want to wait. a

var a = 1
var b = a
b = 2

// a = 1, b = 2
console.log(a === b) // false
Copy the code

But what about reference types?

var a = { x: 1 }
var b = a

b.x = 2

// a = { x: 2 }, b = { x: 2 }
console.log(a === b) // true
Copy the code

At this point, we both refer to the same memory cell as A and B, and when we change the property value in one reference, all references change. The other is that React won’t refresh render if the object points to the same object during shallow comparisons, even if your value has changed. Therefore, most of the time, an array of objects is updated with a single update.

this.setState({ ... state,count: 2
})
Copy the code

The above method generates a new memory reference that is set up to ensure that the new value of the reference type data is relatively unrelated to the current value. The problem was solved, but the way it was solved was very violent. Therefore, in order to manage data more accurately, immer is introduced to manage reference state and reduce unnecessary state changes and unexpected render.

I left a little question at the back, which you can think about. I will not elaborate on this article. Hey hey

Why shallow comparison and not deep comparison?

The Class components

The state update in the class component is done by triggering setState, which is a very simple way to change the state of an object. Modify the data directly in Produce, is there vuex flavor?

More down to earth from a code development perspective.

At first, I just wanted to change one property, but you told me to change that property you need to think about its references, you need to return a new object and so on. But, back to the problem, I just wanted to change the properties, I didn’t want to think about that much.


EthicalAds: A privacy-focused ad network for developers. Publishers & Advertisers wanted!
Ad by EthicalAds
egghead.io lesson 8: Using Immer with useState. Or: useImmer
Deep updates in the state of React components can be greatly simplified as well by using immer. Take for example the following onClick handlers (Try in codesandbox):

/** * Classic React.setState with a deep merge */
onBirthDayClick1 = () = > {
    this.setState(prevState= > ({
        user: {
            ...prevState.user,
            age: prevState.user.age + 1}}}))/**
 * ...But, since setState accepts functions,
 * we can just create a curried producer and further simplify!
 */
onBirthDayClick2 = () = > {
    this.setState(
        produce(draft= > {
            draft.user.age += 1}}))Copy the code

In the above example, in fact, many students will not understand, why there is no raw data? So how do you know which data source you want to represent?

For this question, think of currying, thanks to currying of functions. Its effect is very simple, it is to take a function that takes multiple arguments into a function that takes one argument and returns the rest of the arguments.

Simple example: foo(1, 2, 3) => f(1)(2)(3)

In this sense, since the default setState is passed a state parameter, after Currization, you only need to pass a draft function to change the state.

Function component

For stateless components, immer separates the possible use of State into hook, named use-immer.

  • How to use it? To view the document

useImmer

UseImmer is essentially a custom hook that wraps useState around the object handling of state. For state changes, just pass a function like class.

import { useImmer } from 'use-immer';

const [person, setPerson] = useImmer({
  name: "wangly"});// button click event ...
const handleClick = () = > {
 setPerson(state= > {
 	state.name = 'wangly19 yes!!! '})}Copy the code

Realize the principle of

export function useImmer(initialValue: any) {
  const [val, updateValue] = useState(initialValue);
  return [
    val,
    useCallback(updater= >{ updateValue(produce(updater)); }, [])]; }Copy the code

The use of state is more brief and understandable, and there is no need to worry about the side effects of changing other functions. The data can be modified in detail during development, and there is no need to deliberately create a new object. These things are more unified and easier to locate errors.

Second, for useReducer, and useState half, there is also a custom hook to manage this content. The usage is also relatively easy to understand. Refer to the following example to make different changes when invoking different action operations through Dispatch.

The code is much cleaner and less manipulative than it used to be when creating new objects manually. I don’t worry about some side effects causing problems.

import { useImmerReducer } from "use-immer"; const initialState = { count: 0 }; function reducer(draft, action) { switch (action.type) { case "reset": return initialState; case "increment": return void draft.count++; case "decrement": return void draft.count--; }}Copy the code

Code examples click to see

other

Since the project uses UMi-CLI, it is necessary to use DVA to manage part of the state of the project. If you want to experience IMMER, you only need to add a declaration in the configuration file to enjoy the immer data flow.

import { defineConfig } from 'umi';
import routes from './routes'

export default defineConfig({
  hash: true.antd: {},dva: {
    immer: true
  },
  history: {
    type: 'browser'
  },
  locale: {
    // default zh-CN
    default: 'zh-CN'.antd: true,
  },
  routes
})
Copy the code

performance

In terms of performance, immer doesn’t seem to have a huge cost difference in most scenarios, according to some official comparisons. The opposite view can be.

# wangly19 @ wangly19s-MacBook-Pro in~/Desktop/ project/git:master o [11:42:31] 
$ yarn test:perfYarn run v1.22.5$ cd __performance_tests__ && babel-node add-data.js && babel-node todo.js && babel-node incremental.js

# add-data - loading large set of data

just mutate: 0ms
just mutate, freeze: 1ms
handcrafted reducer (no freeze): 0ms
handcrafted reducer (with freeze): 0ms
immutableJS: 65ms
immutableJS + toJS: 34ms
seamless-immutable: 40ms
seamless-immutable + asMutable: 48ms
immer (proxy) - without autofreeze * 10000: 27ms
immer (proxy) - with autofreeze * 10000: 29ms
immer (es5) - without autofreeze * 10000: 93ms
immer (es5) - with autofreeze * 10000: 65ms

# todo - performance

just mutate: 1ms
just mutate, freeze: 195ms
deepclone, then mutate: 175ms
deepclone, then mutate, then freeze: 368ms
handcrafted reducer (no freeze): 19ms
handcrafted reducer (with freeze): 19ms
naive handcrafted reducer (without freeze): 19ms
naive handcrafted reducer (with freeze): 42ms
immutableJS: 5ms
immutableJS + toJS: 164ms
seamless-immutable: 52ms
seamless-immutable + asMutable: 143ms
immer (proxy) - without autofreeze: 64ms
immer (proxy) - with autofreeze: 74ms
immer (proxy) - without autofreeze - with patch listener: 87ms
immer (proxy) - with autofreeze - with patch listener: 84ms
immer (es5) - without autofreeze: 257ms
immer (es5) - with autofreeze: 257ms
immer (es5) - without autofreeze - with patch listener: 2439ms
immer (es5) - with autofreeze - with patch listener: 2715ms

# incremental - lot of small incremental changes

just mutate: 0ms
handcrafted reducer: 72ms
immutableJS: 17ms
immer (proxy): 988ms
immer (es5): 3520ms
immer (proxy) - single produce: 9ms
immer (es5) - single produce: 3ms
✨  Done in 67.52s.
Copy the code

The resources

  • immerjs github
  • immerjs docs
  • Immutabl – Wikipedia
  • immutablejs

The latter

Immer itself has no performance barrier, and even Posting performance tests at the end of the document doesn’t make much of a difference. Suffice it to say that in some scenarios it is better than today’s solutions. If you’re having problems with your project, see if you can solve them better.

For me, introducing immer is more about dealing with the burden of immutable data. You don’t need to change data implicitly to cause a BUG, even if it’s rare, but if it happens just once, It’s very difficult to detect.

At the end of the day, I wish you all a very strong 2021.

Study together in 2021, you can join me in my little nest and get up early to punch in and brush the questions. See information wechat, simple answer can join, hey hey.