Why do we need reselect

Time:2021-5-5

Why do we need reselect

Problems encountered

Let’s take a look at the following component

import React, { Component } from 'react'
import { connect } from 'react-redux'

class UnusedComp extends Component {
    render() {
        const { a, b, c, fab, hbc, gac, uabc } = this.props
        return (
            <div>
                <h6>{a}</h6>
                <h6>{b}</h6>
                <h6>{c}</h6>
                <h6>{fab}</h6>
                <h6>{hbc}</h6>
                <h6>{gac}</h6>
                <h6>{uabc}</h6>
            </div>
        )
    }
}

function f(x, y) {
    return a + b
}

function h(x, y) {
    return x + 2 * y
}

function g(x, y) {
    return 2 * x + y
}

function u(x, y, z) {
    return x + y + z
}

This unusedcomp component is concerned with such props: A, B, C, f (a, b), H (B, c), G (a, c), u (a, B, c), where f, h, G, u are functions. What should we do with these calculated values?

The data is directly calculated in redux

First, we store all values in redux. The structure of all stores is like this:

store = {
    a:1,
    b:1,
    c:1,
    fab: 2, // a + b
    hbc: 3, // b + 2c
    gac: 3, // 2a + c
    uabc: 3 // a + b + c
}

In this way, our component is simple, just need to render directlyconst { a, b, c, fab, hbc, gac, uabc } = this.props. So the question is, what should the function of reducer do? The corresponding results are as follows:

switch(action.type) {
    case 'changeA': {
        return {
            ...state,
            a: action.a,
            fab: f(action.a, state.b),
            gac: g(action.a, state.c)
            uabc: u(action.a, state.b, state.c)
        }
    }
    case 'changeB': {
        ...
    }
    case 'changeC': {
        ...
    }
}

Our reducer function is very complex. We update each state value. You have to maintain the value related to this value, otherwise there will be data inconsistency.

Reducer only stores the most basic state

In order to ensure the clarity of data flow, the update is simple. We only store the most basic state in redux. The structure of store and redcuer function are as follows:

store = {
    a:1,
    b:1,
    c:1,
}
...
switch(action.type) {
    case 'changeA': {
        return {
            ...state,
            a: action.a
        }
    }
    ...
}

At the moment, the component might look like this:

class UnusedComp extends Component {
    render() {
        const { a, b, c } = this.props
        const fab = f(a, b)
        const hbc = h(b, c)
        const gac = g(a, c)
        const uabc = u(a, b, c)
        return (
            <div>
                <h6>{a}</h6>
                <h6>{b}</h6>
                <h6>{c}</h6>
                <h6>{fab}</h6>
                <h6>{hbc}</h6>
                <h6>{gac}</h6>
                <h6>{uabc}</h6>
            </div>
        )
    }
}

Or something like this:

class UnusedComp extends Component {
    componentWillReciveProps(nextProps) {
        const { a, b, c } = this.props
        this.fab = f(a, b)
        this.hbc = h(b, c)
        this.gac = g(a, c)
        this.uabc = u(a, b, c)
    }
    

    render() {
        const { a, b, c } = this.props
        return (
            <div>
                <h6>{a}</h6>
                <h6>{b}</h6>
                <h6>{c}</h6>
                <h6>{this.fab}</h6>
                <h6>{this.hbc}</h6>
                <h6>{this.gac}</h6>
                <h6>{this.uabc}</h6>
            </div>
        )
    }
}

For the first case, the calculation is performed when the component ownprops (component’s own properties, not Redux passing) or setstate.
For the second clock case, when the component ownprops changes, the calculation is performed.
And both violate our basic principles:Keep component logic simple

Let the data logic leave the component!

//It can be written as a functional component
class UnusedComp extends Component {
    render() {
        const { a, b, c, fab, hbc, gac, uabc } = this.props
        return (
            <div>
                <h6>{a}</h6>
                <h6>{b}</h6>
                <h6>{c}</h6>
                <h6>{fab}</h6>
                <h6>{hbc}</h6>
                <h6>{gac}</h6>
                <h6>{uabc}</h6>
            </div>
        )
    }
}
function mapStateToProps(state) {
    const {a, b, c} = state
    return {
        a,
        b,
        c,
        fab: f(a,b),
        hbc: h(b,c),
        gac: g(a,c),
        uabc: u(a, b, c)
    }
}
UnusedComp = connect(mapStateToProps)(UnusedComp)

Component is very simple, receiving data display is OK. It looks beautiful! We know that when the store data is changed, all connected components will be notified (provided that they are not destroyed).
Suppose that there are three components a, B and C on the page. Any change in the state of these three components (the state of Redux) will start the execution of F, h, G and u here… That sounds ridiculous!!! It’s really ridiculous( In Redner and will reciveprops, the calculation will not cause function execution. But this is usually not a problem, because we generally have only one container component for each page to interact with Redux, and other sub components get data and actions through props. Moreover, react router will destroy the components of the previous route when switching routes. In this way, there will only be one container component at a time.

In one case, suppose that unusedcomp has x, y, Z state attributes, and there is redux. These three attributes are simple three values, which are only used to display. But when x, y, Z change, the calculation will also be triggered. The calculation that happens here can’t be avoided no matter in render, will reciveprops or mapstatetoprops.

Precise control calculation

It’s as if we’ve found a way:

  1. Redux only stores the basic state

  2. React router + single container component

The reality is cruel! In fact, there must be a lot of properties like x, y, Z. This alone will lead to a lot of invalid calculations. The three methods discussed earlier (render, will receive, mapstatetoprops) cannot avoid this kind of calculation.

In addition, mapstatetoprops will be affected by the value changes of other stores. After all, react router + single container component is the best way to organize. Some of our services are in the consideration of performance. We did not destroy the components of the previous routing and used our own routing. Some pages are not single container components, embarrassing!!

Obviously, we know that the changes of X, y and Z need not be calculated, while the changes of a, B and C need to be calculated. How to describe the program? In addition, mapstatetoprops also brings benefits. When we describe it, we will not invade the component!!.

The original description:

let memoizeState = null
function mapStateToProps(state) {
    const {a, b, c} = state
    if (!memoizeState) { 
       memoizeState =  {
            a,
            b,
            c,
            fab: f(a,b),
            hbc: h(b,c),
            gac: g(a,c),
            uabc: u(a, b, c)
        }
    } else {
        if (!(a === memoizeState.a && b === memoizeState.b) ) {
            // f should invoke
            memoizeState.fab = f(a, b)
        }
        if (!(b === memoizeState.b && c === memoizeState.c) ) {
            // h should invoke
            memoizeState.hbc = h(b, c)
        }
        if (!(a === memoizeState.a && c === memoizeState.c) ) {
            // g should invoke
            memoizeState.gac = g(a, c)
        }
        if (!(a === memoizeState.a && b === memoizeState.b && c === memoizeState.c) ) {
            // u should invoke
            memoizeState.uabc = u(a, b, c)
        }
        memoizeState.a = a
        memoizeState.b = b
        memoizeState.c = c
    }
    
    return memoizeState
}

First, we know that the value of fab is related to a and B, so when a and B change, f needs to be re executed. Similarly, functions must be executed only when necessary.

Using reselect

Reselect solves the above problem. We don’t need to use the original description every time. The corresponding reselect description is like this

import { createSelector } from 'reselect'

fSelector = createSelector(
    a => state.a,
    b => state.b,
    (a, b) => f(a, b)
)
hSelector = createSelector(
    b => state.b,
    c => state.c,
    (b, c) => h(b, c)
)
gSelector =  createSelector(
    a => state.a,
    c => state.c,
    (a, c) => g(a, c)
)
uSelector = createSelector(
    a => state.a,
    b => state.b,
    c => state.c,
    (a, b, c) => u(a, b, c)
)

...
function mapStateToProps(state) {
    const { a, b, c } = state
    return {
        a,
        b,
        c,
        fab: fSelector(state),
        hbc: hSelector(state),
        gac: gSelector(state),
        uabc: uSelector(state)
    }
}

In the createselector, we first define the input selector function, and finally define how the value is calculated. The selector guarantees that when the input selector returns equal results, it will not be calculated.

last

If you are a react router and a single container component. Then it is possible to calculate in mapstatetoprops, and the performance problem is not big. And performance should not be the first thing we should consider. The first thing we should consider is simplicity, especially the simplicity of components. When our business is complex enough to consider performance, reselect is a good choice for us!

Recommended Today

Analysis of super comprehensive MySQL statement locking (Part 1)

A series of articles: Analysis of super comprehensive MySQL statement locking (Part 1) Analysis of super comprehensive MySQL statement locking (Part 2) Analysis of super comprehensive MySQL statement locking (Part 2) Preparation in advance Build a system to store heroes of the Three KingdomsheroTable: CREATE TABLE hero ( number INT, name VARCHAR(100), country varchar(100), PRIMARY […]