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.