Reprinted to Immutable. Js with React and Redux practices
This article will focus on Immutable and Redux project practices, and will explain Immutable and Redux in many ways: It includes what Immutable is, why it should be used, Immutable. Js and React, Redux and resELECT combine practice and optimization, and finally summarize some possible problems and solutions to use Immutable.
The index
- Why Immutable
- Immutability, side effects and mutations
- Immutable. Js and React
- Immutable. Js and story
- redux-immutable
- react-router-redux
- Immutable. Js and Redux practice
- JavaScript objects are converted to Immutable objects
- Immutable and Redux state tree
- Immutable versus the Redux component
- An Immutable object is converted to a JavaScript object
- Immutable. Js and reselect
- Problems in Immutable practice
Immutable
Immutable comes from the world of functional programming. We might call it Immutable. Consider the following code:
var object = { x:1, y: 2 };
var object2 = { x: 1, y: 2 };
object == object2// false
object === object2 // false
Copy the code
The equality check will consist of two parts:
Value checking References Checking References Checking JavaScript objects are very complex data structures whose keys can point to any value, including Object. The object created by JavaScript is stored in computer memory (corresponding to a physical address), and it returns a reference that the JavaScript engine can access, and that reference is assigned to a variable through which we can manipulate the object as a reference. Reference checking checks whether the reference addresses of two objects are the same.
Value check Layer by layer to check whether the value of each property of the object is the same.
React re-renders React performs change checks on component properties (props) and state (state) to determine whether to update and re-render the component. If the component state is too large, component performance degrades because the more complex the object, the slower the equality check.
For nested objects, iteration layers must be checked and judged, which takes too long. If only the attributes of the object are modified, its references remain unchanged, and the result of reference check in equality check remains unchanged. Immutable provides a simple and fast way to determine whether objects have changed, which helps with React component updates and rerendering performance.
Immutable data Never mutated, instead copy it and then make change.
Never make sudden changes to the object. First copy and then modify the copied object, and then return the new object, leaving the original object unchanged.
The main differences between Immutable and native JavaScript objects can be summarized as follows:
- Persistent Data Structures
- Structures Sharing Trie
Persistent data structures
Persistent data structures assert that all operations return an updated copy of the data structure, leaving the original structure unchanged rather than changing it. Trie is usually used to build its immutable persistent data structure. Its overall structure can be regarded as a tree, and a tree node can represent a certain attribute of the object. The node value is the attribute value.
Structure of Shared
Once an Immutable Trie object is created, we can think of the Trie object as a tree that reuses as many nodes as possible in subsequent object changes: To update an Immutable property is to reconstruct a node in the Trie tree. To modify a node in the Trie tree, we reconstruct only the node and its affected ancestors, such as the four green nodes in the figure above. The other nodes can be reused completely.
reference
Immutable Persistent Data Structures Trie
Why Immutable
The previous section briefly explained what Immutable is, and this section explains why it is necessary to use it.
We do not encourage sudden object changes, as this often interrupts time travel and bug-related debugging, and state changes in the React-Redux connect method lead to poor component performance:
Time travel: The Redux DevTools developer expects that the application will only return a status value when it reinitiates a history action without changing anything, that is, without side effects. Mutations and asynchronous operations result in chaotic time travel and unpredictable behavior. The react-redux: connect method checks if the props object returned by the mapStateToProps method has changed to determine if the component needs to be updated. To improve the performance of checking for changes, the CONNECT method is modified based on Immutabe state objects to detect changes using shallow reference equality checks. This means that direct changes to objects or arrays cannot be detected, resulting in components that cannot be updated. Other side effects such as generating unique ids or timestamps in the Reducer function can also make the application state unpredictable and difficult to debug and test.
If a reducer function of Redux returns a mutable state object, it means that we cannot track and predict the state, which may lead to redundant updates of the component, re-rendering or no response when updates are needed, and also makes it difficult to track debugging bugs. Immutable. Js provides an Immutable solution to the above problems, and its rich API is sufficient to support complex development.
reference
Why and When to use Immutable Why do we need Immutable class
How to use Immutable
Immutable provides a significant performance boost to our applications, but it must be used correctly, otherwise it is not worth the cost. There are several libraries for Immutable, but for React applications, Immutable is preferred.
Immutable. Js and React
The first thing to understand is that the React component state must be a native JavaScript Object, not an Immutable Object, because the React setState method expects to accept an Object and then merge it with the previous state Object using object. assign.
class Component extends React.Component { Constructor (props) { super(props) this.state = { data: Immutable.Map({ count:0, todos: List() }) } this.handleAddItemClick = this.handleAddItemClick.bind(this) } handleAddItemClick () { this.setState(({data}) => { data: data.update('todos', todos => todos.push(data.get('count'))) }) } render () { const data = this.state.data; Return (<div> <button οnclick={this.handleAddItemClick}></button> <ul> {data.get('todos'). Map (item => <li>Saved: {item}</li> )} </ul> </div> ) } }Copy the code
Access state using the Immutable.js access API, such as get(), getIn();
Generate component child elements using the collection operation Immutable. Js:
Use higher-order functions such as map() and reduce() to create children of the React element:
{data.get('todos').map(item => <li>Saved: {item}</li>)} Update state using Immutable. Enclosing setState (({data}) = > ({data: data update (' count ', v = > v + 1)})) or enclosing setState (({data}) = > ({data: data.set('count', data.get('count') + 1) }));Copy the code
reference
Immutable as React state
Immutable. Js and story
React itself is a JavaScript library that focuses on the view layer, so its states are generally not too complex when used alone, so its collaboration with Immutable. Js is relatively simple. More importantly, we need to pay more attention to its collaboration with the React application state management container. Here’s how Immutable. Js works effectively with Redux.
In Redux we refer to state primarily as application state, not component state.
redux-immutable
The original Redux combineReducers method expects to accept native JavaScript objects and it handles state as a native object, so when we use the createStore method and accept an Immutable object for the initial application state, Reducer will return an error with the following source code:
if (! isPlainObject(inputState)) { return ( `The ${argumentName} has unexpected type of "` + ({}).toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] + ".Expected argument to be an object with the following + `keys:"${reducerKeys.join('", "')}"` ) }Copy the code
As indicated above, the state parameter accepted by the original reducer type should be a native JavaScript object. We need to enhance combineReducers to be able to handle Immutable objects. Redux-immutable is used to create a Redux combineReducers that can work with immutable.
const StateRecord = Immutable.Record({
foo: 'bar'
});
const rootReducer = combineReducers({
first: firstReducer
}, StateRecord);
Copy the code
react-router-redux
RouteReducer can’t handle Immutable. If we use the React-router-redux library in our project, we need to know that routeReducer cannot handle Immutable.
import Immutable from 'immutable'; import { LOCATION_CHANGE } from 'react-router-redux'; const initialState = Immutable.fromJS({ locationBeforeTransitions: null }); export default (state = initialState, action) => { if (action.type === LOCATION_CHANGE) { return state.set('locationBeforeTransitions', action.payload); } return state; }; When we connect the history object to the store using the syncHistoryWithStore method, we need to convert the routing payload into a JavaScript object, Pass a selectLocationState argument to syncHistoryWithStore as follows: import {browserHistory} from 'react-router'; import { syncHistoryWithStore } from 'react-router-redux'; const history = syncHistoryWithStore(browserHistory, store, { selectLocationState (state) { return state.get('routing').toJS(); }});Copy the code
Immutable. Js and Redux practice
When collaborating with Immutable. Js and Redux, we can think about our practice in several ways.
Do not mix native JavaScript objects with Immutable objects;
When adding a JavaScript object to an Immutable object, we first convert the JavaScript object to Immutable using the fromJS() method, Then update Immutable objects using update apis such as Update (), merge(), and set();
// avoid
const newObj = { key: value }
const newState = state.setIn(['prop1'], newObj)
// newObj has been added as a plain JavaScript object, NOT as an Immutable.JS Map
// recommended
const newObj = { key: value }
const newState = state.setIn(['prop1'], fromJS(newObj))
Copy the code
Immutable and Redux state tree
Use Immutable to represent the entire Redux state tree;
For a Redux application, the complete state tree should be represented by an Immutable object, without native JavaScript objects.
Create the state tree using the fromJS() method
A state tree object can be an Immutable.Record or any other instance of an Immutable collection that implements the GET, set, and withMutations methods.
Adjust the combineReducers method to handle IMmutable using the redux-immutable library.
Immutable versus the Redux component
When using Redux for React state management containers, we usually classify components as container and presentation components, and the practice of imdux and Redux components revolves around both.
Manipulate state objects Immutable everywhere except in presentation components;
To ensure application performance, Immutable is used everywhere in container components, selectors, reducer functions, action creators, sagas, and Thunks, but not in presentation components.
Use Immutable within container components
Container components can access redux’s store using the connect method provided by React-Redux, so we need to ensure that selectors always return Immutable objects; otherwise, unnecessary rerendering will occur. In addition, third-party library cache selectors such as ResELECT can be used to improve performance in some scenarios.
An Immutable object is converted to a JavaScript object
The toJS() method converts an Immutable object to a JavaScript object, and we usually do our best to convert Immutable objects to JavaScript objects in container components, which is consistent with the purpose of container components. In addition, the toJS method has extremely low performance, and its use should be limited as much as possible, such as in the mapStateToProps method and in presentation components.
Never use the toJS() method inside the mapStateToProps method
The toJS() method returns a native JavaScript object each time it is called. If you use the toJS() method inside the mapStateToProps method, then every time the Immutable state tree changes, Whether or not the JavaScript object returned by the toJS() method has actually changed, the component will assume that the object has changed, resulting in unnecessary rerendering.
Never use the toJS() method inside a presentation component
If you pass a prop of Immuatble object type to a component, then the rendering of that component depends on that Immutable object, which makes component reuse, testing, and refactoring more difficult.
When a container component passes Immutable properties (props) to a presentation component, it uses a higher-order component (HOC) to transform it into a native JavaScript object.
The higher-order component is defined as follows:
import React from 'react' import { Iterable } from 'immutable' export const toJS = WrappedComponent => wrappedComponentProps => { const KEY = 0 const VALUE = 1 const propsJS = Object.entries(wrappedComponentProps) .reduce((newProps, wrappedComponentProp) => { newProps[wrappedComponentProp[KEY]] = Iterable.isIterable(wrappedComponentProp[VALUE]) ? wrappedComponentProp[VALUE].toJS() : wrappedComponentProp[VALUE] return newProps }, {}) return <WrappedComponent {... propsJS} /> }Copy the code
Inside this higher-order component, the object.entries method is used to iterate over the props of the incoming component, and then the toJS() method is used to convert Immutable prop inside the component into JavaScript objects. This higher-order component can be used within container components as follows:
import { connect } from 'react-redux'
import { toJS } from './to-js'
import DumbComponent from './dumb.component'
const mapStateToProps = state => {
return {
// obj is an Immutable object in Smart Component, but it’s converted to a plain
// JavaScript object by toJS, and so passed to DumbComponent as a pure JavaScript
// object. Because it’s still an Immutable.JS object here in mapStateToProps, though,
// there is no issue with errant re-renderings.
obj:getImmutableObjectFromStateTree(state)
}
}
export default connect(mapStateToProps)(toJS(DumbComponent))
Copy the code
This type of higher-order component does not cause much performance degradation because higher-order components are only called again when the properties of the connected component (usually the presentation component) change. If using the toJS() method in higher-order components is bound to cause some performance degradation, you might ask, why not keep using Immutable in presentation components as well? In fact, maintainability, reusability, and testability by avoiding Immutable infiltration into presentation components are more important than the performance penalty of using toJS() methods in higher-order components.
reference
Immutable.js Best practices
Immutable. Js and reselect
reselect
When using Redux to manage React application state, the mapStateToProps method is an important part of the process of getting data from the Redux Store. It must not have any performance drawbacks. The calculation process is usually based on the story Store state tree, the more complex and obvious Redux state tree, this process may be more time-consuming, we should be able to minimize the calculation process, such as repeated in the same state rendering component, repeated calculation process is obviously redundant, whether we can cache the results? The solution to this problem is ResELECT, which can improve the performance of applications fetching data.
Reselect works by directly using the last cached result as long as the relevant state remains unchanged.
The selector
Reselect creates selectors that take a state argument and then return an item of data that we need to return within the mapStateToProps method. The processing of a selector can be divided into two steps:
If the returned result is consistent with the calculation result of the first step, it indicates that the cache is hit. The calculation result of the second step is directly returned instead of the second step; otherwise, the calculation of the second step continues. The first step of the result comparison, usually just a === equality check, is sufficient performance.
Compute and return the final result based on the result returned in the first step.
TODO, for example, has the following selector functions:
import { createSelector } from 'reselect' import { FilterTypes } from '.. /constants' export const selectFilterTodos = createSelector( [getTodos, getFilters], (todos, filters) => { switch(filters) { case FilterTypes.ALL: return todos; case FilterTypes.COMPLETED: return todos.filter((todo) => todo.completed) default: return todos } } )Copy the code
As above, the createSelector method accepts two parameters:
The first parameter is an array of mapping functions. The data processed in the first step of the selector processing process is the return value of each function in the array. These return values are also passed as parameters to the second step processing function. The second argument is the concrete calculation function of the second step, which returns the data required by the mapStateToProps method. Then use the selector function inside mapStateToProps, taking the state argument:
const mapStateToProps = (state) => {
return {
todos: selectFilterTodos(state)
}
}
Copy the code
The content of the mapping function above is as follows:
const getTodos = (state) => {state.todos}
const getFilter = (state) => {state.filter}
Copy the code
In combination with Immutable. Js
As in the previous example, reselect can be used independently of Immutable, and if Immutable is used, we need to make some additional modifications to make Immutable work in conjunction with reselect.
First, modify the mapping function:
const getTodos = (state) => {state.get('todos')}
const getFilter = (state) => {state.get('filter')}
Copy the code
It is important to note that in the second step of the selector function, the Immutable operation may be involved, and it is also necessary to modify the corresponding Immutable method.
Problems in Immutable practice
In any case, there is no perfect thing or technology, and using Immutable. It is important to avoid these problems, or to differentiate them as much as possible, and to develop the advantages of Immutable. The most common problems with using Immutable are as follows.
Internal collaboration is difficult
The huge differences between Immutable and JavaScript objects make it difficult to collaborate between them, which is where many of the problems come from.
By using Immutable, we can no longer use the dot and parenthesis methods to access object properties. Instead, we can only use the GET and getIn API methods provided by Immutable. You can no longer use the deconstruction and expansion operators provided by ES6; Difficult to work with third-party libraries like LoDash and JQuery. Permeate the entire code base
Immutable code penetrates the entire project, and this strong reliance on external libraries imposes constraints later in the project, after which it is difficult to remove or replace Immutable code.
Not suitable for simple state objects that change frequently
Immutable provides great performance improvements for complex data, but not for simple, frequently changing data.
Disconnecting object references results in poor performance
The biggest advantage of Immutable is that shallow comparisons can greatly improve performance. When we use the toJS method multiple times, even though the objects have not actually changed, the equivalence checks between them fail, causing rerendering. More importantly, if we were to use toJS inside the mapStateToProps method, it would be extremely bad for component performance. If we really need to, we should use the higher-order component approach transformation described earlier.
Difficult to debug
When we examine an Immutable Object, the browser will print the entire nested structure of Immutable. Js, but we only need a small part of it. This makes debugging difficult and can be solved by using the Immutable Object Formatter browser plugin.