Interpretación del código fuente de react-redux

El
pegamento EDITORIAL react-redux como lo mismo, no parece entender que es necesario, pero de hecho, como la conexión de la capa de datos (redux) y la capa de interfaz de usuario (reaccionar), y sus detalles de implementación tienen un efecto decisivo en el rendimiento general. El costo de la actualización aleatoria del árbol de componentes es mucho más alto que el costo de ejecutar el árbol reductor varias veces, por lo que es necesario comprender los detalles de implementación.

Uno de los beneficios de comprender cuidadosamente react-redux es que puede tener una comprensión básica del desempeño. Considere una pregunta:


¿Cuál es el impacto en el rendimiento de esta línea de código en una esquina del árbol de componentes de despacho ({type: 'UPDATE_MY_DATA', payload: myData}) ? Varios subproblemas:

1. ¿Qué reductores se han recalculado?

2. ¿Desde qué componente comienza la actualización de la vista activada?

3. ¿Qué componente se llama renderizado?

4. ¿Todos los componentes de la hoja se han visto afectados por diff? ¿por qué?

Si no puede responder estas preguntas con precisión, definitivamente no tendrá idea sobre el rendimiento.

1. Función En
primer lugar, está claro que redux es solo una capa de datos, y react es solo una capa de interfaz de usuario, no hay conexión entre las dos

Si las manos izquierda y derecha sostienen redux y reaccionan respectivamente, entonces la situación real debería ser así:

Redux ha establecido la estructura de datos (estado) y el método de cálculo (reductor) de cada campo.

React renderiza la página inicial de acuerdo con la descripción de la vista (Componente)

Podría verse así:


       redux      |      react

myUniversalState  |  myGreatUI
  human           |    noOneIsHere
    soldier       |
      arm         |
    littleGirl    |
      toy         |
  ape             |    noOneIsHere
    hoho          |
  tree            |    someTrees
  mountain        |    someMountains
  snow            |    flyingSnow

Hay todo en redux a la izquierda, pero React no lo sabe, solo se muestran los elementos predeterminados (sin datos), hay algunos estados parciales de componentes y accesorios dispersos, la página es como una imagen estática y el árbol de componentes se ve como algunas tuberías. Estante grande conectado

Ahora consideramos agregar react-redux, entonces será así:


             react-redux
       redux     -+-     react

myUniversalState  |  myGreatUI
            HumanContainer
  human          -+-   humans
    soldier       |      soldiers
            ArmContainer
      arm        -+-       arm
    littleGirl    |      littleGirl
      toy         |        toy
            ApeContainer
  ape            -+-   apes
    hoho          |      hoho
           SceneContainer
  tree           -+-   Scene
  mountain        |     someTrees
  snow            |     someMountains
                         flyingSnow

Tenga en cuenta que la interacción del brazo es más complicada y no es adecuada para el control de la capa superior (HumanContainer), por lo que hay un contenedor anidado

El contenedor entrega el estado en redux para reaccionar, de modo que los datos iniciales estén disponibles, entonces, ¿qué pasa si la vista necesita ser actualizada?

Arm.dispatch ({type: 'FIRST_BLOOD', payload: warData})
Alguien disparó el primer tiro, provocando que el soldado cuelgue un (cambio de estado), luego estas partes deben cambiar:


                react-redux
          redux     -+-     react
myNewUniversalState  |  myUpdatedGreatUI
              HumanContainer
     human          -+-   humans
       soldier       |      soldiers
                     |      diedSoldier
                ArmContainer
         arm        -+-       arm
                     |          inactiveArm

Un soldado muerto y un brazo caído (vista de actualización) aparecen en la página, y todo lo demás está bien (simio, escena)

La descripción anterior es el papel de react-redux:

Pasa el estado de redux para reaccionar

Y es responsable de actualizar la vista de reacción después del cambio de estado de redux

Entonces lo adivinaste, la implementación se divide en 3 partes:

Agregue una pequeña fuente de agua al estante grande conectado por la tubería (inyecte el estado como accesorios en la vista inferior a través del contenedor)

Deje que la pequeña fuente de agua riegue (controle el cambio de estado, actualice la vista a continuación a través del setState del contenedor)

No hay una pequeña fuente de agua, no se apresure (optimización de rendimiento incorporada, compare el estado en caché y los accesorios para ver si es necesario actualizar)

2. La clave para lograr las
partes clave del código fuente son las siguientes:


// from: src/components/connectAdvanced/Connect.onStateChange
onStateChange() {
  // state change时重新计算props
  this.selector.run(this.props)

  // 当前组件不用更新的话,通知下方container检查更新
  // 要更新的话,setState空对象强制更新,延后通知到didUpdate
  if (!this.selector.shouldComponentUpdate) {
    this.notifyNestedSubs()
  } else {
    this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate
    // 通知Container下方的view更新
//!!! 这里是把redux与react连接起来的关键
    this.setState(dummyState)
  }
}

El setState más importante está aquí. El secreto de la actualización de la vista después de la acción de envío es este:


1.dispatch action
2.redux计算reducer得到newState
3.redux触发state change(调用之前通过store.subscribe注册的state变化监听器)
4.react-redux顶层Container的onStateChange触发
  1.重新计算props
  2.比较新值和缓存值,看props变了没,要不要更新
  3.要的话通过setState({})强制react更新
  4.通知下方的subscription,触发下方关注state change的Container的onStateChange,检查是否需要更新view

En el paso 3, la acción de react-redux registrando el monitoreo de cambio de tienda con redux ocurre cuando connect () (myComponent). De hecho, react-redux solo monitorea directamente el cambio de estado de redux para el contenedor de nivel superior, y los contenedores de nivel inferior todos transmiten notificaciones internamente. , Como sigue:


// from: src/utils/Subscription/Subscription.trySubscribe
trySubscribe() {
  if (!this.unsubscribe) {
    // 没有父级观察者的话,直接监听store change
    // 有的话,添到父级下面,由父级传递变化
    this.unsubscribe = this.parentSub
      ? this.parentSub.addNestedSub(this.onStateChange)
      : this.store.subscribe(this.onStateChange)
  }
}

Aquí no monitoreamos directamente el cambio de estado de redux, en lugar de mantener el oyente de cambio de estado del contenedor por nosotros mismos, para lograr un orden controlable, como se mencionó anteriormente:


// 要更新的话,延后通知到didUpdate
this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate

Esto asegura que la secuencia de activación del oyente esté en el orden de la jerarquía del árbol de componentes. Primero se notifica al subárbol grande, y después de que se actualiza el subárbol grande, se notifica al subárbol pequeño para que se actualice.

Todo el proceso de actualización es así. En cuanto al paso de "inyectar el estado como accesorios en la vista inferior a través del contenedor", no hay nada que decir, como sigue:


// from: src/components/connectAdvanced/Connect.render
render() {
  return createElement(WrappedComponent, this.addExtraProps(selector.props))
}

Cree un accesorio basado en el campo de estado requerido por WrappedComponent e inyéctelo a través de React.createElement. Cuando ContainerInstance.setState ({}), se vuelve a llamar a la función de renderizado, se inyectan nuevos accesorios en la vista, la vista recibirá accesorios ... la actualización de la vista realmente comenzará

3. Técnicas para
hacer que las funciones puras tengan un estado


function makeSelectorStateful(sourceSelector, store) {
  // wrap the selector in an object that tracks its results between runs.
  const selector = {
    run: function runComponentSelector(props) {
      try {
        const nextProps = sourceSelector(store.getState(), props)
        if (nextProps !== selector.props || selector.error) {
          selector.shouldComponentUpdate = true
          selector.props = nextProps
          selector.error = null
        }
      } catch (error) {
        selector.shouldComponentUpdate = true
        selector.error = error
      }
    }
  }

  return selector
}

Envuelva una función pura con un objeto para tener un estado local, que es similar a la nueva instancia de clase. De esta manera, la parte pura se separa de la impura. Lo puro sigue siendo puro y lo impuro está afuera. La clase no es tan limpia como esta.

Parámetros predeterminados y deconstrucción de objetos


function connectAdvanced(
  selectorFactory,
  // options object:
  {
    getDisplayName = name => `ConnectAdvanced(${name})`,
    methodName = 'connectAdvanced',
    renderCountProp = undefined,
    shouldHandleStateChanges = true,
    storeKey = 'store',
    withRef = false,
    // additional options are passed through to the selectorFactory
    ...connectOptions
  } = {}
) {
  const selectorFactoryOptions = {
    // 展开 还原回去
    ...connectOptions,
    getDisplayName,
    methodName,
    renderCountProp,
    shouldHandleStateChanges,
    storeKey,
    withRef,
    displayName,
    wrappedComponentName,
    WrappedComponent
  }
}

Puede simplificarse a esto:


function f({a = 'a', b = 'b', ...others} = {}) {
    console.log(a, b, others);
    const newOpts = {
      ...others,
      a,
      b,
      s: 's'
    };
    console.log(newOpts);
}
// test
f({a: 1, c: 2, f: 0});
// 输出
// 1 "b" {c: 2, f: 0}
// {c: 2, f: 0, a: 1, b: "b", s: "s"}

Aquí hay 3 consejos de es6 +:

Parámetros predeterminados. Evitar errores indefinidos en el lado derecho durante la deconstrucción

Deconstrucción de objetos. Envuelva los atributos restantes en el objeto de otros

Expandir operador. Expandir otros y combinar atributos con el objeto de destino

Los parámetros predeterminados son características de es6, no hay nada que decir. La deconstrucción de objetos es la propuesta de la Etapa 3, ... otros es su uso básico. El operador de expansión expande el objeto y lo fusiona con el objeto de destino, lo cual no es complicado

Lo que es más interesante es que la combinación de operadores de expansión y deconstrucción de objetos se usa aquí para realizar este escenario en el que los parámetros deben empaquetarse y restaurarse. Si no se usan estas dos características, es posible que deba hacer esto:


function connectAdvanced(
  selectorFactory,
  connectOpts,
  otherOpts
) {
  const selectorFactoryOptions = extend({},
    otherOpts,
    getDisplayName,
    methodName,
    renderCountProp,
    shouldHandleStateChanges,
    storeKey,
    withRef,
    displayName,
    wrappedComponentName,
    WrappedComponent
  )
}

Debe distinguir claramente entre connectOpts y otherOpts. Será más complicado de implementar. Si combina estas técnicas, el código es bastante conciso

También hay un consejo es6 +:


addExtraProps(props) {
  //! 技巧 浅拷贝保证最少知识
  //! 浅拷贝props,不把别人不需要的东西传递出去,否则影响GC
  const withExtras = { ...props }
}

Una referencia más significa un riesgo más de pérdida de memoria, y no se debe dar a lo que no se necesita (conocimiento mínimo)

Coincidencia de patrones de parámetros

function match(arg, factories, name) {
  for (let i = factories.length - 1; i >= 0; i--) {
    const result = factories[i](arg)
    if (result) return result
  }

  return (dispatch, options) => {
    throw new Error(`Invalid value of type ${typeof arg} for ${name} argument when connecting component ${options.wrappedComponentName}.`)
  }
}

Las fábricas son así:


// mapDispatchToProps
[
  whenMapDispatchToPropsIsFunction,
  whenMapDispatchToPropsIsMissing,
  whenMapDispatchToPropsIsObject
]
// mapStateToProps
[
  whenMapStateToPropsIsFunction,
  whenMapStateToPropsIsMissing
]

Cree una serie de funciones de caso para varias condiciones de los parámetros, y luego deje que los parámetros fluyan a través de todos los casos uno por uno, y devuelva el resultado si alguno coincide, e ingrese el caso de error si no hay coincidencia

Similar al caso del interruptor, se utiliza para realizar la coincidencia de patrones en los parámetros, de modo que se descomponen varios casos y sus respectivas responsabilidades son claras (el nombre de cada función de caso es muy preciso)

Parámetros perezosos


function wrapMapToPropsFunc() {
  // 猜完立即算一遍props
  let props = proxy(stateOrDispatch, ownProps)
  // mapToProps支持返回function,再猜一次
  if (typeof props === 'function') {
    proxy.mapToProps = props
    proxy.dependsOnOwnProps = getDependsOnOwnProps(props)
    props = proxy(stateOrDispatch, ownProps)
  }
}

Entre ellos, los parámetros perezosos se refieren a:


// 把返回值作为参数,再算一遍props
if (typeof props === 'function') {
  proxy.mapToProps = props
  proxy.dependsOnOwnProps = getDependsOnOwnProps(props)
  props = proxy(stateOrDispatch, ownProps)
}

Esta implementación está relacionada con el escenario al que se enfrenta react-redux El soporte de la función de retorno es principalmente para soportar el control detallado de mapToProps a nivel de instancia de componente (el valor predeterminado es nivel de componente). De esta manera, se pueden asignar diferentes mapToProps a diferentes instancias de componentes para apoyar una mayor mejora del rendimiento.

Desde el punto de vista de la implementación, equivale a retrasar los parámetros reales y admite el paso de una fábrica de parámetros como un parámetro. El entorno externo se pasa a la fábrica por primera vez, y la fábrica luego crea los parámetros reales de acuerdo con el entorno. Con la adición del enlace de fábrica, la granularidad del control se refina (el nivel de componente se refina al nivel de instancia de componente y el entorno externo es la información de instancia de componente)

PD Para discusiones relacionadas sobre parámetros perezosos, consulte https://github.com/reactjs/react-redux/pull/279

Cuatro Preguntas
1. ¿De dónde vino el envío de props.dispatch predeterminado?


connect()(MyComponent)

Sin pasar ningún parámetro para conectarse, la instancia de MyComponent también puede obtener un accesorio llamado dispatch. ¿Dónde se colgó en secreto?


function whenMapDispatchToPropsIsMissing(mapDispatchToProps) {
  return (!mapDispatchToProps)
    // 就是这里挂上去的,没传mapDispatchToProps的话,默认把dispatch挂到props上
    ? wrapMapToPropsConstant(dispatch => ({ dispatch }))
    : undefined
}

Un mapDispatchToProps = dispatch => ({dispatch}) está integrado de forma predeterminada, por lo que hay un envío en los accesorios de los componentes. Si se especifica mapDispatchToProps, no se bloqueará

2. ¿El contenedor de varios niveles enfrentará problemas de rendimiento?
Considere este escenario:


App
  HomeContainer
    HomePage
      HomePageHeader
        UserContainer
          UserPanel
            LoginContainer
              LoginButton

Cuando aparezcan contenedores anidados, ¿se repetirá la actualización de la vista muchas veces cuando cambie el estado afectado por HomeContainer? como:


HomeContainer update-didUpdate
UserContainer update-didUpdate
LoginContainer update-didUpdate

Si este es el caso, un despacho ligero hará que los 3 subárboles se actualicen y parezca que el rendimiento está a punto de explotar.

Este no es el caso. Para los contenedores de varios niveles, existe la situación de ir dos veces, pero ir dos veces aquí no se refiere a la actualización de la vista, sino a la notificación de cambio de estado.

El contenedor superior notificará al contenedor inferior para buscar actualizaciones después de didUpdate, y puede volver a pasar por el subárbol pequeño. Pero en el proceso de actualización del subárbol grande, cuando va al contenedor inferior, el subárbol pequeño comienza a actualizarse en este momento. La notificación después del subárbol grande didUpdate solo permitirá que el contenedor inferior pase por la verificación sin actualizaciones reales.

El costo específico de la inspección es comparar el estado y los accesorios con === comparación y comparación de referencia superficial (también === comparación primero), y se termina si no hay cambios, por lo que el costo de rendimiento de cada contenedor de nivel inferior es dos === Comparación, no importa. En otras palabras, no se preocupe por la sobrecarga de rendimiento de usar contenedores anidados

5.
Dirección de Github de análisis de código fuente : https://github.com/ayqy/react-redux-5.0.6

Los comentarios de PS todavía son suficientemente detallados.

Supongo que te gusta

Origin blog.51cto.com/15080030/2592683
Recomendado
Clasificación