Почему нам нужно повторно выбрать

React.js Redux

Почему нам нужно повторно выбрать

возникшие проблемы

Взгляните на следующие компоненты

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
}

Компонент UnusedComp заботится о нескольких реквизитах: a, b, c, f(a,b), h(b, c), g(a, c), u(a, b, c), где f, h , g, u — функция соответственно. Что нам делать с этими рассчитанными значениями?

Вычислять данные непосредственно в редуксе

Во-первых, мы храним все значения в редуксе, и структура всех хранилищ, вероятно, такая:

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

Таким образом, наш компонент прост, нам нужно только напрямую отобразить значениеconst { a, b, c, fab, hbc, gac, uabc } = this.props. Итак, вопрос в том, как следует обрабатывать функцию редуктора? Соответствующие следующие:

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': {
        ...
    }
}

Наша редукторная функция очень сложная, каждый раз, когда мы обновляем значение состояния. Все должны поддерживать значение, связанное с этим значением, иначе возникнут несоответствия данных.

Редуктор хранит только самое основное состояние

Чтобы обеспечить ясность потока данных, обновление выполняется просто. Мы храним только самое основное состояние в редуксе. Структура хранилища и функция редуктора следующие:

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

На данный момент компонент может выглядеть так:

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>
        )
    }
}

или это:

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>
        )
    }
}

В первом случае расчет будет производиться при вызове компонента ownProps (собственные свойства компонента, не переданные по редуксу) или setState.
Во втором случае вычисление выполняется при изменении свойства ownProps компонента.
И то, и другое нарушает наши основные принципы:Сохраняйте логику компонента простой

Держите логику данных вне компонентов!

// 可以写成函数式组件
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)

Компонент очень простой, достаточно получить отображение данных. Смотрится красиво! Мы знаем, что при изменении данных хранилища все подключенные компоненты будут уведомлены (при условии, что оно не уничтожено).
Все гипотетические страницы имеют три компонента: A, B и C. Любое изменение состояния этих трех компонентов (состояние, в котором существует избыточность) вызовет здесь выполнение f, h, g и u. . . Звучит смешно! ! ! Это действительно смешно! (В Redner вычисление в willReceiveProps не приведет к выполнению функции здесь). Но обычно это не проблема, потому что обычно у нас есть только один компонент-контейнер на странице, взаимодействующий с избыточностью, а другие подкомпоненты получают данные и действия через реквизиты. И когда react-router переключает маршруты, он уничтожает компоненты предыдущего маршрута. Таким образом, одновременно будет только один компонент контейнера.

Рассмотрим ситуацию, предполагая, что UnusedComp также имеет атрибуты состояния x, y, z, есть избыточность. Эти 3 свойства — это просто 3 значения, которые используются только для отображения. Но при изменении x, y, z также запускается расчет. Вычисления, которое здесь происходит, нельзя избежать, вычисляется ли оно в рендере, в willReciveProps или в mapStateToProps.

Точный контроль расчетов

Как будто мы нашли способ, основанный на:

  1. redux сохраняет только основное состояние
  2. react-router + компонент с одним контейнером

Реальность очень жестока! На самом деле свойства x, y и z должны существовать в большом количестве. Это само по себе может привести к большому количеству неверных вычислений. 3 рассмотренных ранее метода (render, willReceive, mapStateToProps) не могут избежать этого расчета.

Кроме того, на mapStateToProps также будут влиять изменения значений других хранилищ, ведь организация react-router + компонент single container component — это только в лучшем случае. Некоторые из наших бизнесов основаны на соображениях производительности.Мы не уничтожаем ранее маршрутизированные компоненты и используем свои собственные маршруты. Некоторые страницы также не являются одноконтейнерными компонентами, что смущает! !

Очевидно, мы знаем, что изменения x, y, z вычислять не нужно, а нужно вычислять изменения a, b и c. Как это описать программе? Кроме того, метод mapStateToProps тоже приносит пользу, когда мы его описываем, мы не будем вторгаться в компонент! ! .

Оригинальное описание:

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
}

Во-первых, мы знаем, что значение fab связано с a, b, поэтому при изменении a, b необходимо повторно выполнить f. Точно так же функция должна выполняться только тогда, когда это необходимо.

использовать повторный выбор

reselect решает нашу проблему выше, и нам не нужно каждый раз использовать это самое примитивное описание, соответствующее описание reselect выглядит так

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)
    }
}

В createSelector мы сначала определяем функцию селектора ввода и, наконец, определяем, как вычисляется значение. Селектор гарантирует, что когда селектор ввода возвращает тот же результат, он не будет вычисляться.

наконец

Если вы являетесь реактивным маршрутизатором и одним компонентом контейнера. Тогда его можно вычислить в mapStateToProps, и проблема с производительностью невелика. И производительность не должна быть первым, о чем мы думаем, наше первое соображение — это простота, особенно простота компонентов. Когда наш бизнес достаточно сложен, чтобы учитывать производительность, повторный выбор — наш хороший выбор!