Interpretation des React-Redux-Quellcodes


Das, was im vorherigen React-Redux als Kleber geschrieben wurde, scheint unnötig, aber tatsächlich haben die Implementierungsdetails als Verbindung zwischen der Datenschicht (Redux) und der UI-Schicht (React) einen entscheidenden Einfluss auf die Gesamtleistung. Die Kosten für die zufällige Aktualisierung des Komponentenbaums sind viel höher als die Kosten für die mehrmalige Ausführung des Reduzierungsbaums. Daher ist es erforderlich, die Implementierungsdetails zu verstehen

Einer der Vorteile eines sorgfältigen Verständnisses von React-Redux besteht darin, dass Sie ein grundlegendes Verständnis der Leistung haben können. Stellen Sie sich eine Frage:


Welche Auswirkungen hat diese Codezeile auf die Leistung in einer Ecke des Komponentenbaums des Versands ({Typ: 'UPDATE_MY_DATA', Nutzlast: myData}) ? Mehrere Unterprobleme:

1. Welche Reduzierungen wurden neu berechnet?

2. Von welcher Komponente aus startet die ausgelöste Ansichtsaktualisierung?

3. Welche Komponente wird gerendert?

4. Wurde jede Blattkomponente von Diff betroffen? Warum?

Wenn Sie diese Fragen nicht genau beantworten können, haben Sie definitiv keine Ahnung von der Leistung.

1. Funktion
Zunächst ist klar, dass Redux nur eine Datenschicht ist und React nur eine UI-Schicht, es besteht keine Verbindung zwischen beiden

Wenn die linke und die rechte Hand Redux halten und reagieren, sollte die tatsächliche Situation folgendermaßen aussehen:

Redux hat die Datenstruktur (Status) und die Berechnungsmethode (Reduzierer) für jedes Feld festgelegt.

React rendert die Startseite gemäß der Ansichtsbeschreibung (Komponente)

Es könnte so aussehen:


       redux      |      react

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

Links befindet sich alles in Redux, aber React weiß nicht, es werden nur die Standardelemente angezeigt (keine Daten), es gibt einige Teilzustände der Komponenten und verstreute Requisiten, die Seite ist wie ein statisches Bild und der Komponentenbaum sieht genauso aus wie einige Pipes. Verbundenes großes Regal

Jetzt überlegen wir, React-Redux hinzuzufügen, dann wird es so:


             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

Beachten Sie, dass die Arminteraktion komplizierter ist und nicht für die Steuerung durch die obere Schicht (HumanContainer) geeignet ist, sodass ein verschachtelter Container vorhanden ist

Der Container übergibt den Status in Redux, um zu reagieren, sodass die ursprünglichen Daten verfügbar sind. Was ist also, wenn die Ansicht aktualisiert werden muss?

Arm.dispatch ({Typ: 'FIRST_BLOOD', Nutzlast: warData})
Jemand hat den ersten Schuss abgefeuert und den Soldaten dazu gebracht, einen ( Statuswechsel) aufzuhängen. Dann müssen sich diese Teile ändern:


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

Ein toter Soldat und ein fallengelassener Arm (Update-Ansicht) erscheinen auf der Seite, und alles andere ist in Ordnung (Affe, Szene)

Die obige Beschreibung ist die Rolle von React-Redux:

Übergeben Sie den Status von Redux, um zu reagieren

Und ist verantwortlich für die Aktualisierung der Reaktionsansicht nach einer Änderung des Redux-Status

Dann haben Sie es erraten, die Implementierung ist in 3 Teile unterteilt:

Fügen Sie dem großen Regal, das durch die Rohrleitung verbunden ist, eine kleine Wasserquelle hinzu (injizieren Sie den Zustand als Requisiten in die untere Ansicht durch den Behälter).

Lassen Sie die kleine Wasserquelle Wasser (überwachen Sie auf Zustandsänderung, aktualisieren Sie die Ansicht unten durch den setState des Containers)

Keine kleine Wasserquelle, keine Eile (integrierte Leistungsoptimierung, Vergleich des zwischengespeicherten Status und der Requisiten, um festzustellen, ob eine Aktualisierung erforderlich ist)

2. Der Schlüssel zum Erreichen der
wichtigsten Teile des Quellcodes lautet wie folgt:


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

Der wichtigste setState ist hier. Das Geheimnis der Aktualisierung der Ansicht nach der Versandaktion ist Folgendes:


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

In Schritt 3 erfolgt die Aktion der Überwachung der Speicheränderung durch React-Redux-Registrierung mit Redux, wenn connect () (myComponent). React-Redux überwacht die Statusänderung von Redux für den Container der obersten Ebene nur direkt, und die Container der unteren Ebene senden alle Benachrichtigungen intern. , Wie folgt:


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

Hier überwachen wir die Statusänderung von Redux nicht direkt, anstatt den Statusänderungs-Listener des Containers selbst zu verwalten, um eine steuerbare Reihenfolge zu erreichen, wie oben erwähnt:


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

Dadurch wird sichergestellt, dass die Triggersequenz des Listeners in der Reihenfolge der Komponentenbaumhierarchie liegt. Der große Teilbaum wird zuerst benachrichtigt, und nachdem der große Teilbaum aktualisiert wurde, wird der kleine Teilbaum zur Aktualisierung benachrichtigt.

Der gesamte Aktualisierungsprozess sieht folgendermaßen aus. Was den Schritt "Status als Requisiten durch Container in die untere Ansicht injizieren" betrifft, gibt es nichts zu sagen:


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

Erstellen Sie Requisiten basierend auf dem für WrappedComponent erforderlichen Statusfeld und fügen Sie es über React.createElement ein. Wenn ContainerInstance.setState ({}) die Renderfunktion erneut aufruft, neue Requisiten in die Ansicht eingefügt werden, erhält die Ansicht Requisiten ... die Aktualisierung der Ansicht beginnt wirklich

3. Techniken, um reine Funktionen zu
machen, haben Zustand


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
}

Schließen Sie eine reine Funktion mit einem Objekt ein, um einen lokalen Status zu erhalten, der der neuen Klasseninstanz ähnelt. Auf diese Weise wird der reine Teil vom unreinen Teil getrennt. Der reine Teil ist immer noch rein und der unreine ist draußen. Die Klasse ist nicht so sauber wie diese

Standardparameter und Objektdekonstruktion


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

Kann vereinfacht werden:


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

Hier sind 3 es6 + Tipps:

Standardparameter. Verhindern Sie undefinierte Fehler auf der rechten Seite während der Dekonstruktion

Objektdekonstruktion. Wickeln Sie die verbleibenden Attribute in das andere Objekt ein

Erweitern Sie Operator. Erweitern Sie andere und führen Sie Attribute zum Zielobjekt zusammen

Die Standardparameter sind es6-Funktionen, es gibt nichts zu sagen. Objektdekonstruktion ist der Vorschlag der Stufe 3, ... andere ist seine grundlegende Verwendung. Der Erweiterungsoperator erweitert das Objekt und führt es mit dem Zielobjekt zusammen, was nicht kompliziert ist

Interessanter ist, dass hier die Kombination aus Objektdekonstruktions- und Erweiterungsoperatoren verwendet wird, um dieses Szenario zu realisieren, in dem Parameter gepackt und wiederhergestellt werden müssen. Wenn diese beiden Funktionen nicht verwendet werden, müssen Sie möglicherweise Folgendes tun:


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

Sie müssen klar zwischen connectOpts und otherOpts unterscheiden. Die Implementierung ist schwieriger. Wenn Sie diese Techniken kombinieren, ist der Code recht präzise

Es gibt auch 1 es6 + Tipp:


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

Eine weitere Referenz bedeutet ein weiteres Risiko für Speicherlecks und sollte nicht auf das angewendet werden, was nicht benötigt wird (Mindestkenntnisse).

Parametermusterabgleich

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

Die Fabriken sind wie folgt:


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

Erstellen Sie eine Reihe von Fallfunktionen für verschiedene Bedingungen der Parameter, lassen Sie die Parameter nacheinander durch alle Fälle fließen, geben Sie das Ergebnis zurück, wenn einer übereinstimmt, und geben Sie den Fehlerfall ein, wenn keine Übereinstimmung vorliegt

Ähnlich wie bei Switch-Case wird es verwendet, um einen Musterabgleich für Parameter durchzuführen, so dass verschiedene Fälle zerlegt werden und ihre jeweiligen Verantwortlichkeiten klar sind (die Benennung jeder Fallfunktion ist sehr genau).

Faule Parameter


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

Unter diesen beziehen sich faule Parameter auf:


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

Diese Implementierung bezieht sich auf das Szenario, mit dem React-Redux konfrontiert ist. Die Rückgabefunktion unterstützt hauptsächlich feinkörnige mapToProps-Steuerelemente auf Komponenteninstanzebene (Standard ist Komponentenebene). Auf diese Weise können verschiedenen Komponenteninstanzen unterschiedliche mapToProps zugewiesen werden, um eine weitere Leistungsverbesserung zu unterstützen

Aus Sicht der Implementierung entspricht dies der Verzögerung der tatsächlichen Parameter und unterstützt die Übergabe einer Parameterfactory als Parameter. Die externe Umgebung wird zum ersten Mal an die Factory übergeben, und die Factory erstellt dann die tatsächlichen Parameter entsprechend der Umgebung. Durch Hinzufügen der Factory-Verknüpfung wird die Steuerungsgranularität verfeinert (die Komponentenebene wird auf die Komponenteninstanzebene verfeinert, und die externe Umgebung ist die Komponenteninstanzinformation).

PS Weitere Informationen zu verzögerten Parametern finden Sie unter https://github.com/reactjs/react-redux/pull/279

Viertens Fragen
1. Woher kam die Standard-Requisiten-Versandanzeige?


connect()(MyComponent)

Ohne die Übergabe von Parametern für die Verbindung kann die MyComponent-Instanz auch eine Requisite namens "dispatch" abrufen. Wo hat sie sich heimlich aufgehängt?


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

Ein mapDispatchToProps = dispatch => ({dispatch}) ist standardmäßig integriert, sodass die Komponenten-Requisiten versendet werden. Wenn mapDispatchToProps angegeben ist, hängt es nicht

2. Werden bei mehrstufigen Containern Leistungsprobleme auftreten?
Stellen Sie sich dieses Szenario vor:


App
  HomeContainer
    HomePage
      HomePageHeader
        UserContainer
          UserPanel
            LoginContainer
              LoginButton

Wenn verschachtelte Container angezeigt werden, wird die Ansichtsaktualisierung viele Male wiederholt, wenn sich der von HomeContainer betroffene Status ändert? sowie:


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

Wenn dies der Fall ist, werden die 3 Teilbäume durch einen leichten Versand aktualisiert, und es scheint, als würde die Leistung gleich explodieren.

Das ist nicht der Fall. Bei mehrstufigen Containern besteht die Situation, zweimal zu gehen, aber zweimal zu gehen bezieht sich hier nicht auf die Aktualisierung der Ansicht, sondern auf die Benachrichtigung über Statusänderungen

Der obere Container benachrichtigt den unteren Container, um nach didUpdate nach Updates zu suchen, und durchläuft möglicherweise erneut den kleinen Teilbaum. Während der Aktualisierung des großen Teilbaums, wenn er in den unteren Container verschoben wird, beginnt der kleine Teilbaum zu diesem Zeitpunkt mit der Aktualisierung. Durch die Benachrichtigung nach dem großen Teilbaum didUpdate wird der untere Container nur ohne tatsächliche Aktualisierungen die Prüfung durchlaufen.

Die spezifischen Kosten der Inspektion bestehen darin, den Zustand und die Requisiten mit dem Vergleich und dem flachen Referenzvergleich zu vergleichen (auch der Vergleich zuerst). Wenn keine Änderung erfolgt, sind die Leistungskosten für jeden untergeordneten Container zwei === Vergleich spielt keine Rolle. Mit anderen Worten, machen Sie sich keine Sorgen über den Leistungsaufwand bei der Verwendung verschachtelter Container

5. Quellcode-Analyse
Github-Adresse: https://github.com/ayqy/react-redux-5.0.6

PS-Kommentare sind noch ausreichend detailliert.

Ich denke du magst

Origin blog.51cto.com/15080030/2592683
Empfohlen
Rangfolge