React ist schnell! Ein Teil dieser Geschwindigkeit kommt davon, dass nur die Teile des DOM aktualisiert werden, die es benötigen. Weniger worüber Sie sich Sorgen machen müssen und ein Geschwindigkeitsvorteil obendrein. Solange Sie die Funktionsweise von setState() verstehen, sind Sie gut aufgestellt. Es ist jedoch auch wichtig, sich damit vertraut zu machen, *wie* diese erstaunliche Bibliothek das DOM Ihrer Anwendung aktualisiert. Dies zu wissen wird für Ihre Arbeit als React-Entwickler von entscheidender Bedeutung sein.
Das DOM?
Der Browser erstellt das DOM, indem er den von Ihnen geschriebenen Code parst, bevor er die Seite rendert. Das DOM repräsentiert Dokumente auf der Seite als Knoten und Objekte und bietet eine Schnittstelle, damit Programmiersprachen das DOM einbinden und manipulieren können. Das Problem mit dem DOM ist, dass es nicht für dynamische UI-Anwendungen optimiert ist. Das Aktualisieren des DOM kann Ihre Anwendung verlangsamen, wenn viele Dinge geändert werden müssen; da der Browser alle Stile neu anwenden und neue HTML-Elemente rendern muss. Dies geschieht auch in Situationen, in denen sich nichts ändert.
Was ist Reconciliation?
Reconciliation ist der Prozess, durch den React das DOM aktualisiert. Wenn sich der Status eines Komponenten ändert, muss React berechnen, ob es notwendig ist, das DOM zu aktualisieren. Dies geschieht durch die Erstellung eines *virtuellen* DOM und dessen Vergleich mit dem aktuellen DOM. In diesem Zusammenhang enthält das virtuelle DOM den neuen Zustand der Komponente.
Lassen Sie uns eine einfache Komponente erstellen, die zwei Zahlen addiert. Die Zahlen werden in ein Eingabefeld eingegeben.
Siehe den Pen reconciliation Pen von Kingsley Silas Chijioke (@kinsomicrote) auf CodePen.
Zuerst müssen wir den anfänglichen Zustand für die Felder einrichten und dann den Zustand aktualisieren, wenn eine Zahl eingegeben wird. Die Komponente wird wie folgt aussehen
class App extends React.Component {
state = {
result: '',
entry1: '',
entry2: ''
}
handleEntry1 = (event) => {
this.setState({entry1: event.target.value})
}
handleEntry2 = (event) => {
this.setState({entry2: event.target.value})
}
handleAddition = (event) => {
const firstInt = parseInt(this.state.entry1)
const secondInt = parseInt(this.state.entry2)
this.setState({result: firstInt + secondInt })
}
render() {
const { entry1, entry2, result } = this.state
return(
<div>
<div>
<p>Entry 1: { entry1 }</p>
<p>Entry 2: { entry2 }</p>
<p>Result: { result }</p>
</div>
<br />
<div>
<span>Entry 1: </span>
<input type='text' onChange={this.handleEntry1} />
</div>
<br />
<div>
<span>Entry 2: </span>
<input type='text' onChange={this.handleEntry2} />
</div>
<div>
<button onClick={this.handleAddition} type='submit'>Add</button>
</div>
</div>
)
}
}
Beim anfänglichen Rendern sieht der DOM-Baum so aus;

Wenn eine Eingabe in das erste Eingabefeld erfolgt, erstellt React einen neuen Baum. Der neue Baum, der das virtuelle DOM ist, enthält den neuen Zustand für entry1. Dann vergleicht React das virtuelle DOM mit dem alten DOM und ermittelt aus dem Vergleich den Unterschied zwischen beiden DOMs und nimmt eine Aktualisierung nur für den Teil vor, der unterschiedlich ist. Jedes Mal, wenn sich der Zustand der App-Komponente ändert – wenn ein Wert in eines der Eingabefelder eingegeben wird oder wenn der Button geklickt wird –, wird ein neuer Baum erstellt.

Unterschiede bei unterschiedlichen Elementen
Wenn sich der Zustand einer Komponente so ändert, dass ein Element von einem Typ in einen anderen geändert werden muss, unmountet React den gesamten Baum und erstellt einen neuen von Grund auf neu. Dies führt dazu, dass jeder Knoten in diesem Baum zerstört wird.
Sehen wir uns ein Beispiel an
class App extends React.Component {
state = {
change: true
}
handleChange = (event) => {
this.setState({change: !this.state.change})
}
render() {
const { change } = this.state
return(
<div>
<div>
<button onClick={this.handleChange}>Change</button>
</div>
{
change ?
<div>
This is div cause it's true
<h2>This is a h2 element in the div</h2>
</div> :
<p>
This is a p element cause it's false
<br />
<span>This is another paragraph in the false paragraph</span>
</p>
}
</div>
)
}
}
Beim anfänglichen Rendern sehen Sie das div und seinen Inhalt und wie das Klicken auf den Button dazu führt, dass React den Baum des div mit seinem Inhalt zerstört und stattdessen einen Baum für das <p>-Element erstellt. Dasselbe passiert, wenn wir in beiden Fällen dieselbe Komponente haben. Die Komponente wird zusammen mit dem vorherigen Baum, zu dem sie gehörte, zerstört, und eine neue Instanz wird erstellt. Sehen Sie sich die Demo unten an;
Siehe den Pen reconciliation-2 Pen von Kingsley Silas Chijioke (@kinsomicrote) auf CodePen.
Unterschiede bei Listen
React verwendet Schlüssel (keys), um Elemente in einer Liste zu verfolgen. Die Schlüssel helfen dabei, die Position des Elements in einer Liste zu ermitteln. Was passiert, wenn eine Liste keine Schlüssel hat? React mutiert jedes Kind der Liste, auch wenn es keine neuen Änderungen gibt.
Mit anderen Worten, React ändert jeden Eintrag in einer Liste, die keine Schlüssel hat.
Hier ist ein Beispiel:
const firstArr = ['codepen', 'codesandbox']
const secondArr = ['github', 'codepen', 'bitbucket', 'codesanbox']
class App extends React.Component {
state = {
change: true
}
handleChange = (event) => {
this.setState({change: !this.state.change})
}
render() {
const { change } = this.state
return(
<div>
<div>
<button onClick={this.handleChange}>Change</button>
</div>
<ul>
{
change ?
firstArr.map((e) => <li>{e}</li>)
:
secondArr.map((e) => <li>{e}</li>)
}
</ul>
</div>
)
}
}
Hier haben wir zwei Arrays, die je nach Zustand der Komponente gerendert werden. React hat keine Möglichkeit, die Elemente in der Liste zu verfolgen, daher ist es gezwungen, die gesamte Liste jedes Mal zu ändern, wenn eine Neu-Renderung erforderlich ist. Dies führt zu Leistungsproblemen.
In Ihrer Konsole sehen Sie eine Warnung wie diese
Warning: Each child in an array or iterator should have a unique "key" prop.
Um dies zu beheben, fügen Sie für jedes Element in der Liste einen eindeutigen Schlüssel hinzu. Die beste Lösung in diesem Szenario ist, ein Array von Objekten zu erstellen, wobei jedes Element eine eindeutige id hat. Wenn wir den Array-Index verwenden, ist das ein Antipattern, das uns später schaden wird.
const firstArr = [
{ id: 1, name: 'codepen'},
{ id: 2, name: 'codesandbox'}
]
const secondArr = [
{ id: 1, name: 'github'},
{ id: 2, name: 'codepen'},
{ id: 3, name: 'bitbucket'},
{ id: 4, name: 'codesandbox'}
]
class App extends React.Component {
state = {
change: true
}
handleChange = (event) => {
this.setState({change: !this.state.change})
}
render() {
const { change } = this.state
return(
<div>
<div>
<button onClick={this.handleChange}>Change</button>
</div>
<ul>
{
change ?
firstArr.map((e) => <li key={e.id}>{e.name}</li>)
:
secondArr.map((e) => <li key={e.id}>{e.name}</li>)
}
</ul>
</div>
)
}
}
Siehe den Pen reconciliation-3 Pen von Kingsley Silas Chijioke (@kinsomicrote) auf CodePen.
Zusammenfassend
Zusammenfassend lässt sich sagen, dass dies die beiden wichtigsten Erkenntnisse für das Verständnis der Funktionsweise des Konzepts der Reconciliation in React sind
- React kann Ihre UI schnell machen, aber es braucht Ihre Hilfe. Es ist gut, seinen Reconciliation-Prozess zu verstehen.
- React führt kein vollständiges Neu-Rendern Ihrer DOM-Knoten durch. Es ändert nur das, was es ändern muss. Der Diffing-Prozess ist so schnell, dass Sie ihn vielleicht nicht bemerken.
Die Verwendung des Array-Indexes ist ein Antipattern und würde nur die Warnung beseitigen, ohne das zugrunde liegende Performance-Problem tatsächlich zu lösen. https://medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318
Die Verwendung der Item-ID anstelle der Indexnummer als Schlüssel (key prop) ist effizienter. Auf diese Weise bleiben bei der Löschung eines Elements alle anderen Schlüssel des Arrays unverändert.