Cómo funciona la reconciliación de React | Programar Plus

¡Reaccionar es rápido! Parte de esa velocidad proviene de actualizar solo las partes del DOM que lo necesitan. Menos de qué preocuparse y una ganancia de velocidad para arrancar. Mientras entiendas el funcionamiento de setState(), Usted debe ser bueno para ir. Sin embargo, también es importante familiarizarse con la forma en que esta increíble biblioteca actualiza el DOM de su aplicación. Saber esto será fundamental en su trabajo como desarrollador de React.

¿El DOM?

El navegador construye el DOM analizando el código que escribes, lo hace antes de mostrar la página. El DOM representa documentos en la página como nodos y objetos, proporcionando una interfaz para que los lenguajes de programación puedan conectarse y manipular el DOM. El problema con el DOM es que no está optimizado para aplicaciones de interfaz de usuario dinámicas. Por lo tanto, actualizar el DOM puede ralentizar su aplicación cuando hay muchas cosas que cambiar; ya que el navegador tiene que volver a aplicar todos los estilos y generar nuevos elementos HTML. Esto también sucede en situaciones en las que nada cambia.

¿Qué es la Reconciliación?

La reconciliación es el proceso a través del cual React actualiza el DOM. Cuando cambia el estado de un componente, React tiene que calcular si es necesario actualizar el DOM. Lo hace creando un DOM virtual y comparándolo con el DOM actual. En este contexto, el DOM virtual contendrá el nuevo estado del componente.

Construyamos un componente simple que suma dos números. Los números se introducirán en un campo de entrada.

Vea Pen reconciliation Pen de Kingsley Silas Chijioke (@kinsomicrote) en CodePen.

Primero, necesitaremos configurar el estado inicial de los campos, luego actualizar el estado cuando se ingrese un número. El componente se verá así:

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

En el renderizado inicial, el árbol DOM se verá así;

Una captura de pantalla de DevTools que muestra la representación HTML de la aplicación.

Cuando se realiza una entrada en el primer campo de entrada, React crea un nuevo árbol. El nuevo árbol que es el DOM virtual contendrá el nuevo estado para entry1. Luego, React compara el DOM virtual con el DOM anterior y, a partir de la comparación, descubre la diferencia entre ambos DOM y actualiza solo la parte que es diferente. Se crea un nuevo árbol cada vez que el estado de App cambios de componente: cuando se ingresa un valor en cualquiera de los campos de entrada o cuando se hace clic en el botón.

Gif animado que muestra cómo cambia el marcado en DevTools cuando se agregan números al campo de entrada.

Diferenciar diferentes elementos

Cuando el estado de un componente cambia y es necesario cambiar un elemento de un tipo a otro, React desmonta todo el árbol y crea uno nuevo desde cero. Esto hace que se destruyan todos los nodos de ese árbol.

Veamos un ejemplo:

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

En el renderizado inicial, verá el div y su contenido y cómo hacer clic en el botón hace que React destruya el árbol del div con su contenido y construya un árbol para el <p> elemento en su lugar. Lo mismo sucede si tenemos el mismo componente en ambos casos. El componente se destruirá junto con el árbol anterior al que pertenecía y se construirá una nueva instancia. Vea la demostración a continuación;

Vea Pen reconciliation-2 Pen de Kingsley Silas Chijioke (@kinsomicrote) en CodePen.

Listas de diferencias

React usa claves para realizar un seguimiento de los elementos en una lista. Las teclas lo ayudan a determinar la posición del elemento en una lista. ¿Qué sucede cuando una lista no tiene claves? React mutará a todos los elementos secundarios de la lista incluso si no hay nuevos cambios.

En otras palabras, React cambia todos los elementos de una lista que no tiene claves.

Aquí hay un ejemplo:

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

Aquí, tenemos dos matrices que se procesan según el estado del componente. React no tiene forma de realizar un seguimiento de los elementos de la lista, por lo que está obligado a cambiar la lista completa cada vez que sea necesario volver a renderizar. Esto da como resultado problemas de rendimiento.

En su consola, verá una advertencia como esta:

Warning: Each child in an array or iterator should have a unique "key" prop.

Para solucionar esto, agrega una clave única para cada elemento de la lista. La mejor solución en este escenario es crear una matriz de objetos, cada uno de los cuales tiene un único id. Si hacemos uso del índice de la matriz, eso será un antipatrón, eso volverá a hacernos daño.

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

Vea Pen reconciliation-3 Pen de Kingsley Silas Chijioke (@kinsomicrote) en CodePen.

Terminando

En resumen, estos son los dos puntos principales para comprender cómo funciona el concepto de reconciliación en React:

  • React puede hacer que su interfaz de usuario sea más rápida, pero necesita su ayuda. Es bueno entender su proceso de reconciliación.
  • React no hace una representación completa de sus nodos DOM. Solo cambia lo que necesita. El proceso de diferenciación es tan rápido que es posible que no lo notes.