Capítulo 4. Dentro de la reconciliación
Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com
Para ser realmente fluidos en React, necesitamos entender lo que hacen sus funciones. Hasta ahora, hemos entendido JSX y React.createElement
. También hemos comprendido el DOM virtual con cierto nivel de detalle apreciable. Exploremos sus aplicaciones prácticasen React en este capítulo, y comprendamos lo que hace ReactDOM.createRoot(element).render()
. En concreto, exploraremos cómo React construye su DOM virtual y luego actualiza el DOM real mediante un proceso llamadoconciliación .
Comprender la conciliación
A modo de breve resumen, el DOM virtual de React es un plano del estado deseado de nuestra interfaz de usuario. React toma este plano y, mediante un proceso llamadoreconciliación, lo convierte en realidad en un entorno anfitrión determinado; normalmente un navegador web, pero posiblemente otros entornos como shells, plataformas nativas como iOS y Android, etc.
Considera el siguiente fragmento de código:
import
{
useState
}
from
"react"
;
const
App
=
()
=>
{
const
[
count
,
setCount
]
=
useState
(
0
);
return
(
<
main
>
<
div
>
<
h1
>
Hello
,
world
!<
/h1>
<
span
>
Count
:
{
count
}
<
/span>
<
button
onClick
=
{()
=>
setCount
(
count
+
1
)}
>
Increment
<
/button>
<
/div>
<
/main>
);
};
Este fragmento de código contiene una descripción declarativa de lo que queremos que sea el estado de nuestra interfaz de usuario: un árbol de elementos. Tanto nuestros compañeros como React pueden leer esto y entender que estamos intentando crear una aplicación de contador con un botón que incremente el contador. Para entender la reconciliación, comprendamos qué hace React por dentro cuando se enfrenta a un componente como éste.
En primer lugar, el JSX se convierte en un árbol de elementos React. Esto es lo que vimos en el Capítulo 3. Cuando se invoca, el componente App
devuelve un elemento React cuyos hijos son otros elementos React. Los elementos React son inmutables (para nosotros) y representan el estado deseado de la IU. No son el estado real de la interfaz de usuario. Los elementos React se crean medianteReact.createElement
o el símbolo JSX <
, por lo que esto se transpilaría en:
const
App
=
()
=>
{
const
[
count
,
setCount
]
=
useState
(
0
);
return
React
.
createElement
(
"main"
,
null
,
React
.
createElement
(
"div"
,
null
,
React
.
createElement
(
"h1"
,
null
,
"Hello, world!"
),
React
.
createElement
(
"span"
,
null
,
"Count: "
,
count
),
React
.
createElement
(
"button"
,
{
onClick
:
()
=>
setCount
(
count
+
1
)
},
"Increment"
)
)
);
};
Esto nos daría un árbol de elementos React creados con un aspecto similar al siguiente:
{
type
:
"main"
,
props
:
{
children
:
{
type
:
"div"
,
props
:
{
children
:
[
{
type
:
"h1"
,
props
:
{
children
:
"Hello, world!"
,
},
},
{
type
:
"span"
,
props
:
{
children
:
[
"Count: "
,
count
],
},
},
{
type
:
"button"
,
props
:
{
onClick
:
()
=>
setCount
(
count
+
1
),
children
:
"Increment"
,
},
},
],
},
},
},
}
Este fragmento representa el DOM virtual procedente de nuestro componenteCounter
. Como se trata de la primera renderización, este árbol se envía al navegador utilizando llamadas mínimas a las APIs imperativas del DOM. ¿Cómo garantiza React unas llamadas mínimas a las APIs DOM imperativas? Lo hace agrupando las actualizaciones vDOM en una actualización DOM real, y tocando el DOM lo menos posible por las razones expuestas en capítulos anteriores. Profundicemos un poco más en este tema para comprender mejor la agrupación.
Dosificación
En el Capítulo 3, hablamos de los fragmentos de documento en los navegadores como parte de las API integradas en el DOM: contenedores ligeros que contienen colecciones de nodos del DOM que actúan como un área temporal donde puedes hacer múltiples cambios sin afectar al DOM principal hasta que finalmente añades el fragmento de documento al DOM, desencadenando un único reflujo y repintado.
De forma similar, React agrupa por lotes las actualizaciones de en el DOM real durante la reconciliación, combinando múltiples actualizaciones de vDOM en una sola actualización del DOM. Esto reduce el número de veces que debe actualizarse el DOM real y, por tanto, se presta a un mejor rendimiento de las aplicaciones web.
Para entenderlo, consideremos un componente que actualiza su estado varias veces en rápida sucesión:
function
Example
()
{
const
[
count
,
setCount
]
=
useState
(
0
);
const
handleClick
=
()
=>
{
setCount
((
prevCount
)
=>
prevCount
+
1
);
setCount
((
prevCount
)
=>
prevCount
+
1
);
setCount
((
prevCount
)
=>
prevCount
+
1
);
};
return
(
<
div
>
<
p
>
Count
:
{
count
}</
p
>
<
button
onClick
=
{
handleClick
}>
Increment
</
button
>
</
div
>
);
}
En este ejemplo, la función handleClick
llama a setCount
tres veces en rápida sucesión. Sin el procesamiento por lotes, React actualizaría el DOM real tres veces distintas, aunque el valor de count
sólo cambiara una vez. Esto sería derrochador y lento.
Sin embargo, como React actualiza por lotes, realiza una actualización del DOM con count + 3
en lugar de tres actualizaciones del DOM con count + 1
cada vez.
Para calcular la actualización por lotes más eficiente del DOM, React creará un nuevo árbol vDOM como bifurcación del árbol vDOM actual con los valores actualizados, donde count
es 3
. Este árbol tendrá quereconciliarse con lo que hay actualmente en el navegador, convirtiendo0
en 3
. React calculará entonces que sólo es necesaria una actualización del DOM utilizando el nuevo valor vDOM 3
en lugar de actualizar manualmente el DOM tres veces. Así es como encaja la dosificación en la imagen, y es una parte del tema más amplio en el que estamos a punto de sumergirnos: la reconciliación, o el proceso de reconciliar el siguiente estado esperado del DOM con el DOM actual.
Antes de comprender lo que hace el React actual bajo el capó, exploremos cómo solía realizar React la conciliación antes de la versión 16, con el conciliador "pila" heredado. Esto nos ayudará a comprender la necesidad del popular reconciliador de fibra actual .
Nota
Llegados a este punto, merece la pena mencionar que todos los temas que vamos a tratar son detalles de implementación en React que pueden cambiar con el tiempo, y probablemente lo harán. Aquí estamos aislando el mecanismo de funcionamiento de React del uso práctico real de React. El objetivo es que, al entender los mecanismos internos de React, comprendamos mejor cómo utilizar React eficazmente en aplicaciones .
Estado de la Técnica
Anteriormente, React utilizaba una estructura de datos de pila para la renderización. Para asegurarnos de que estamos en la misma página, vamos a hablar brevemente de la estructura de datos de la pila.
Reconciliador de pilas (heredado)
En informática, una pila es una estructura de datos lineal que sigue el principio de último en entrar, primero en salir (LIFO). Esto significa que el último elemento añadido a la pila será el primero en ser eliminado. Una pila tiene dos operaciones fundamentales, push y pop, que permiten añadir y eliminar elementos de la parte superior de la pila, respectivamente.
Una pila puede visualizarse como una colección de elementos dispuestos verticalmente, siendo el elemento superior el añadido más recientemente. Aquí tienes una ilustración ASCII de una pila con tres elementos:
-----
|
3
|
|
___
|
|
2
|
|
___
|
|
1
|
|
___
|
En este ejemplo, el elemento añadido más recientemente es 3
, que está en la parte superior de la pila. El elemento 1
, que se añadió primero, está en la parte inferior de la pila.
En esta pila, la operación push añade un elemento a la parte superior de la pila. En código, esto puede ejecutarse en JavaScript utilizando una matriz y el método push
, así:
const
stack
=
[];
stack
.
push
(
1
);
// stack is now [1]
stack
.
push
(
2
);
// stack is now [1, 2]
stack
.
push
(
3
);
// stack is now [1, 2, 3]
La operación pop elimina el elemento superior de la pila. En código, esto puede ejecutarse en JavaScript utilizando una matriz y el método pop
, de la siguiente manera:
const
stack
=
[
1
,
2
,
3
];
const
top
=
stack
.
pop
();
// top is now 3, and stack is now [1, 2]
En este ejemplo, el método pop
elimina el elemento superior (3
) de la pila y lo devuelve. La pila contiene ahora los elementos restantes (1
y 2
).
El reconciliador original de React era un algoritmo basado en la pila que se utilizaba para comparar los árboles virtuales antiguo y nuevo y actualizar el DOM en consecuencia. Aunque el reconciliador de pila funcionaba bien en casos sencillos, presentaba una serie de retos a medida que las aplicaciones crecían en tamaño y complejidad.
Veamos rápidamente por qué es así. Para ello, consideraremos un ejemplo en el que tenemos una lista de actualizaciones que hacer:
-
Un componente no esencial, caro desde el punto de vista computacional, consume CPU yrenderiza.
-
Un usuario teclea en un elemento
input
. -
Button
se activa si la entrada es válida. -
Un componente contenedor de
Form
mantiene el estado, por lo que se vuelve a renderizar.
En código, expresaríamos esto así:
import
React
,
{
useReducer
}
from
"react"
;
const
initialState
=
{
text
:
""
,
isValid
:
false
};
function
Form
()
{
const
[
state
,
dispatch
]
=
useReducer
(
reducer
,
initialState
);
const
handleChange
=
(
e
)
=>
{
dispatch
({
type
:
"handleInput"
,
payload
:
e
.
target
.
value
});
};
return
(
<
div
>
<
ExpensiveComponent
/>
<
input
value
=
{
state
.
text
}
onChange
=
{
handleChange
}
/>
<
Button
disabled
=
{
!
state
.
isValid
}>
Submit
</
Button
>
</
div
>
);
}
function
reducer
(
state
,
action
)
{
switch
(
action
.
type
)
{
case
"handleInput"
:
return
{
text
:
action
.
payload
,
isValid
:
action
.
payload
.
length
>
0
,
};
default
:
throw
new
Error
();
}
}
En este caso, el reconciliador de la pila renderizaría las actualizaciones secuencialmente sin poder pausar o aplazar el trabajo. Si el componente caro computacionalmente bloquea la renderización, la entrada del usuario aparecerá en pantalla con un retraso observable. Esto conduce a una mala experiencia de usuario, ya que el campo de texto no respondería. En cambio, sería mucho más agradable poder reconocer la entrada del usuario como una actualización de mayor prioridad que la representación del componente caro no esencial, y actualizar la pantalla para reflejar la entrada, aplazando la representación del componente caro desde el punto de vista informático.
Es necesario poder abandonar el trabajo de renderizado actual si se ve interrumpido por un trabajo de renderizado de mayor prioridad, como la entrada del usuario. Para ello, React necesita tener un sentido de la prioridad para ciertos tipos de operaciones de renderizado sobre otras.
El reconciliador de pilas no priorizaba las actualizaciones , lo que significaba que las actualizaciones menos importantes podían bloquear a las más importantes. Por ejemplo, una actualización de baja prioridad de un tooltip podía bloquear una actualización de alta prioridad de una entrada de texto. Las actualizaciones del árbol virtual se ejecutaban en el orden en que se recibían.
En una aplicación React, las actualizaciones del árbol virtual pueden tener distintos niveles de importancia. Por ejemplo, una actualización de una entrada de formulario puede ser más importante que una actualización de un indicador que muestre el número de me gusta de una publicación, porque el usuario está interactuando directamente con la entrada y espera que responda.
En el reconciliador de pila, las actualizaciones se ejecutaban en el orden en que se recibían, lo que significaba que las actualizaciones menos importantes podían bloquear las actualizaciones más importantes. Por ejemplo, si se recibía una actualización del contador de similitud antes que una actualización de la entrada del formulario, la actualización del contador de similitud se ejecutaría primero y podría bloquear la actualización de la entrada del formulario.
Si la actualización del contador like tarda mucho tiempo en ejecutarse (por ejemplo, porque está realizando un cálculo costoso), esto podría provocar un retraso notable o un jank en la interfaz de usuario, especialmente si el usuario está interactuando con la aplicación durante la actualización.
Otro problema del reconciliador de pilas era que no permitía interrumpir o cancelar las actualizaciones. Esto significa que, aunque el reconciliador de pila tuviera una idea de la prioridad de las actualizaciones, no había garantías de que pudiera funcionar bien con varias prioridades abandonando el trabajo sin importancia cuando seprogramaba una actualización de alta prioridad.
En cualquier aplicación web, no todas las actualizaciones son iguales: que aparezca una notificación inesperada al azar no es tan importante como que responda a mi clic en un botón, porque lo segundo es una acción deliberada que justifica una reacción inmediata, mientras que lo primero ni siquiera se espera y puede que ni siquiera sea bienvenido.
En el reconciliador de pila, las actualizaciones no podían interrumpirse ni cancelarse, lo que significaba que a veces se realizaban actualizaciones innecesarias, como mostrar una tostada, a expensas de las interacciones del usuario. Esto podía provocar que se realizara un trabajo innecesario en el árbol virtual y en el DOM, lo que repercutía negativamente en el rendimiento de laaplicación.
El reconciliador de pilas presentaba una serie de retos a medida que las aplicaciones crecían en tamaño y complejidad. Los principales retos giraban en torno al jank y a la lentitud de respuesta de las interfaces de usuario. Para hacer frente a estos retos, el equipo de React desarrolló un nuevo reconciliador llamado reconciliador de Fibra, que se basa en una estructura de datos diferente llamada árbol de Fibra. Exploremos esta estructura de datos en la siguiente sección.
El Reconciliador de Fibras
El reconciliador Fibra implica el uso de una estructura de datos diferente llamada "Fibra" que representa una única unidad de trabajo para el reconciliador. Las Fibras se crean a partir de los elementos React que tratamos enel Capítulo 3, con la diferencia clave de que son con estado y de larga duración, mientras que los elementos React son efímeros y sin estado.
Mark Erikson, mantenedor de Redux y destacado experto en React, describe las Fibras como "la estructura de datos interna de React que representa el árbol de componentes real en un momento dado". Efectivamente, ésta es una buena forma de pensar sobre las Fibras, y está en la línea de Mark, quien, en el momento de escribir estas líneas, trabaja a tiempo completo en la depuración de aplicaciones React con Replay: una herramienta que te permite rebobinar y reproducir el estado de tu aplicación para depurarla. Si aún no lo has hecho, visita Replay.io para obtener más información.
De forma similar a como el vDOM es un árbol de elementos, React utiliza un árbol de Fibras en la reconciliación que, como su nombre indica, es un árbol de Fibras que se modela directamente a partir del vDOM.
La fibra como estructura de datos
La estructura de datos Fiber en React es un componente clave del reconciliador Fiber. El reconciliador Fiber permite priorizar las actualizaciones y ejecutarlas de forma concurrente, lo que mejora el rendimiento y la capacidad de respuesta de las aplicaciones React. Exploremos la estructura de datos Fibra con más detalle.
En esencia, la estructura de datos Fibra es una representación de una instancia de componente y su estado en una aplicación React. Como ya se ha dicho, la estructura de datos Fibra está diseñada para ser una instancia mutable y puede actualizarse y reorganizarse según sea necesario durante el proceso de reconciliación.
Cada instancia de un nodo de Fibra contiene información sobre el componente que representa, incluyendo sus accesorios, estado y componentes hijos. El nodo Fibra también contiene información sobre su posición en el árbol de componentes, así como metadatos que utiliza el reconciliador Fibra para priorizar y ejecutar actualizaciones.
Aquí tienes un ejemplo de nodo simple de Fibra:
{
tag
:
3
,
// 3 = ClassComponent
type
:
App
,
key
:
null
,
ref
:
null
,
props
:
{
name
:
"Tejas"
,
age
:
30
},
stateNode
:
AppInstance
,
return
:
FiberParent
,
child
:
FiberChild
,
sibling
:
FiberSibling
,
index
:
0
,
//...
}
En este ejemplo, tenemos un nodo Fibra que representa un ClassComponent
llamado App
. El nodo Fibra contiene información sobre el componente
tag
-
En este caso es
3
, que React utiliza para identificar los componentes de clase. Cada tipo de componente (componentes de clase, componentes de función, límites de suspensión y error, fragmentos, etc.) tiene su propio ID numérico como Fibras. type
-
App
se refiere a la función o componente de clase que representa esta Fibra. props
-
(
{name: "Tejas", age: 30}
) representan los puntales de entrada al componente, o argumentos de entrada a la función. stateNode
-
La instancia del componente
App
que representa esta Fibra.Su posición en el árbol de componentes:
return
,child
,sibling
, yindex
proporcionan al reconciliador de Fibras una forma de "recorrer el árbol", identificando padres, hijos, hermanos y el índice de la Fibra.
La reconciliación de Fibras consiste en comparar el árbol de Fibras actual con el árbol de Fibras siguiente y averiguar qué nodos hay que actualizar, añadir o eliminar.
Durante el proceso de reconciliación, el reconciliador de Fibra crea un nodo de Fibra para cada elemento React en el DOM virtual. Hay una función llamada createFiberFromTypeAndProps
que hace esto. Por supuesto, otra forma de decir "tipo y props" es llamándolos elementos React. Como recordamos, un elemento React es esto: type y props:
{
type
:
"div"
,
props
:
{
className
:
"container"
}
}
Esta función devuelve una Fibra derivada de elementos. Una vez creados los nodos de la Fibra, el reconciliador de Fibras utiliza un bucle de trabajo para actualizar la interfaz de usuario. El bucle de trabajo comienza en el nodo Fibra raíz y recorre el árbol de componentes hacia abajo, marcando cada nodo Fibra como "sucio" si necesita actualizarse. Una vez que llega al final, vuelve a subir, creando un nuevo árbol DOM en memoria, separado del navegador, que finalmente se enviará (flushed) a la pantalla. Esto se representa mediante dos funciones: beginWork
camina hacia abajo, marcando los componentes como "necesita actualizarse", ycompleteWork
vuelve hacia arriba, construyendo un árbol de elementos DOM reales separados del navegador. Este proceso de renderizado fuera de la pantalla puede interrumpirse y tirarse en cualquier momento, ya que el usuario no lo ve.
La arquitectura Fiber se inspira en un concepto llamado "doble buffering" en el mundo de los juegos, en el que la siguiente pantalla se prepara fuera de la pantalla y luego se "descarga" en la pantalla actual. Para comprender mejor la arquitectura Fiber, vamos a entender este concepto con un poco más de detalle antes de seguir adelante.
Doble amortiguación
Doble búfer es una técnica utilizada en gráficos por ordenador y procesamiento de vídeo para reducir el parpadeo y mejorar el rendimiento percibido. La técnica consiste en crear dos búferes (o espacios de memoria) para almacenar imágenes o fotogramas, y alternar entre ellos a intervalos regulares para mostrar la imagen final o el vídeo.
Así es como funciona en la práctica el doble buffering:
-
El primer búfer se llena con la imagen o fotograma inicial.
-
Mientras se visualiza el primer búfer, el segundo búfer se actualiza con nuevos datos o imágenes.
-
Cuando la segunda memoria intermedia está lista, se intercambia con la primera y se muestra en la pantalla.
-
El proceso continúa, cambiando la primera y la segunda memoria intermedia a intervalos regulares para mostrar la imagen o el vídeo final.
Al utilizar el doble búfer, se pueden reducir los parpadeos y otros artefactos visuales, ya que la imagen o vídeo final se muestra sin interrupciones ni retrasos.
La reconciliación de Fibra es similar a la doble memoria intermedia, de modo que cuando se producen actualizaciones, el árbol de Fibra actual se bifurca y se actualiza para reflejar el nuevo estado de una determinada interfaz de usuario. A esto se le llama renderización en . Después, cuando el árbol alternativo está listo y refleja con precisión el estado que un usuario espera ver, se intercambia con el árbol actual de forma similar a como se intercambian las memorias de vídeo en la doble memoria intermedia. Esto se denominafase deconfirmación de la reconciliación o confirmación.
Al utilizar un árbol de trabajo en curso, el Reconciliador de Fibras presenta una serie de ventajas:
-
Puede evitar hacer actualizaciones innecesarias en el DOM real, lo que puede mejorar el rendimiento y reducir el parpadeo.
-
Puede calcular el nuevo estado de una interfaz de usuario fuera de la pantalla, y desecharlo si debe producirse una nueva actualización de mayor prioridad.
-
Como la conciliación se produce fuera de la pantalla, puede incluso pausarse y reanudarse sin alterar lo que el usuario ve en ese momento.
Con el reconciliador de Fibras, se derivan dos árboles a partir de un árbol deelementos JSX definido por el usuario: un árbol que contiene las Fibras "actuales" y otro árbol que contiene las Fibras en curso. Exploremos un poco más estos árboles.
Reconciliación de fibras
La reconciliación de la fibra se produce en dos fases: la fase de renderizado y la fase de confirmación. Este enfoque en dos fases, que se muestra en la Figura 4-1, permite a React realizar un trabajo de renderizado del que puede deshacerse en cualquier momento antes de confirmarlo en el DOM y mostrar un nuevo estado a los usuarios: hace que el renderizado sea interrumpible. Para ser un poco más detallista, lo que hace que la renderización parezca interrumpible es la heurística empleada por el programador de React de devolver la ejecución al hilo principal cada 5 ms, que es menor que un solo fotograma incluso en dispositivos de 120 fps.
Profundizaremos más en los detalles del programador en el Capítulo 7, cuando exploremos las funciones concurrentes de React. Por ahora, sin embargo, vamos a recorrer estas fases deconciliación.
La fase de renderizado
La fase de renderizado comienza en cuando se produce un evento de cambio de estado en el árbolcurrent
. React hace el trabajo de realizar los cambios fuera de la pantallaen el árbol alternate
recorriendo recursivamente cada Fibra y estableciendo banderas que señalan que hay actualizaciones pendientes (ver Figura 4-2). Como hemos aludido antes, esto ocurre en una función llamada beginWork
internamente en React.
beginWork
beginWork
es responsable de establecer banderas en los nodos de Fibra del árbol de trabajo en curso sobre si deben o no actualizarse. Establece un montón de banderas y luego va recursivamente al siguiente nodo de Fibra, haciendo lo mismo hasta que llega al final del árbol. Cuando termine, empezamos a llamar a completeWork
en los nodos Fibra y volvemos a subir.
La firma de beginWork
es la siguiente:
function
beginWork
(
current
:
Fiber
|
null
,
workInProgress
:
Fiber
,
renderLanes
:
Lanes
)
:
Fiber
|
null
;
Más adelante hablaremos de completeWork
. Por ahora, vamos a sumergirnos en beginWork
. Su firma incluye los siguientes argumentos:
current
-
Una referencia al nodo Fibra del árbol actual que corresponde al nodo Trabajo en curso que se está actualizando. Se utiliza para determinar qué ha cambiado entre la versión anterior y la nueva versión del árbol, y qué hay que actualizar. Nuncase modifica y sólo se utiliza para comparar.
workInProgress
-
El nodo de la Fibra que se actualiza en el árbol de trabajo en curso. Este es el nodo que se marcará como "sucio" si se actualiza y devuelve la función.
renderLanes
-
Carriles de renderizado es un nuevo concepto del reconciliador de fibra de React que sustituye al antiguo
renderExpirationTime
. Es un poco más complejo que el antiguo conceptorenderExpirationTime
, pero permite a React priorizar mejor las actualizaciones y hacer más eficiente el proceso de actualización. Dado querenderExpirationTime
está obsoleto, nos centraremos enrenderLanes
en este capítulo.Es esencialmente una máscara de bits que representa "carriles" en los que se está procesando una actualización. Los carriles son una forma de categorizar las actualizaciones en función de su prioridad y otros factores. Cuando se realiza un cambio en un componente React, se le asigna un carril en función de su prioridad y otras características. Cuanto mayor sea la prioridad del cambio, mayor será el carril que se le asigne.
El valor
renderLanes
se pasa a la funciónbeginWork
para garantizar que las actualizaciones se procesan en el orden correcto. Las actualizaciones asignadas a las vías de mayor prioridad se procesan antes que las actualizaciones asignadas a las vías de menor prioridad. Esto garantiza que las actualizaciones de alta prioridad, como las que afectan a la interacción con el usuario o a la accesibilidad, se procesen lo más rápidamente posible.Además de priorizar las actualizaciones,
renderLanes
también ayuda a React a gestionar mejor la concurrencia. React utiliza una técnica llamada "time slicing" para dividir las actualizaciones de larga duración en trozos más pequeños y manejables.renderLanes
desempeña un papel clave en este proceso, ya que permite a React determinar qué actualizaciones deben procesarse primero, y qué actualizaciones pueden aplazarse hasta más tarde.Una vez finalizada la fase de renderizado, se llama a la función
getLanesToRetrySynchronouslyOnError
para determinar si se ha creado alguna actualización diferida durante la fase de renderizado. Si hay actualizaciones diferidas, la funciónupdateComponent
inicia un nuevo bucle de trabajo para gestionarlas, utilizandobeginWork
ygetNextLanes
para procesar las actualizaciones y darles prioridad en función de sus carriles.Profundizaremos mucho más en los carriles de renderizado en el Capítulo 7, el próximo capítulo sobre concurrencia. Por ahora, continuemos siguiendo el flujo de reconciliación de Fibra.
completarTrabajo
La función completeWork
aplica actualizaciones al nodo Fibra de trabajo en curso y construye un nuevo árbol DOM real que representa el estado actualizado de la aplicación. Construye este árbol separado del DOM fuera del plano de visibilidad del navegador.
Si el entorno anfitrión es un navegador, esto significa hacer cosas comodocument.createElement
o newElement.appendChild
. Ten en cuenta que este árbol de elementos aún no está unido al documento dentro del navegador: React sólo está creando la siguiente versión de la interfaz de usuario fuera de la pantalla. Hacer este trabajo fuera de la pantalla lo hace interrumpible: cualquiera que sea el siguiente estado que React está calculando, aún no está pintado en la pantalla, por lo que puede desecharse en caso de que se programe alguna actualización de mayor prioridad. Este es el objetivo delReconciliador de Fibras.
La firma de completeWork
es la siguiente:
function
completeWork
(
current
:
Fiber
|
null
,
workInProgress
:
Fiber
,
renderLanes
:
Lanes
)
:
Fiber
|
null
;
Aquí, la firma es la misma firma que beginWork
.
La función completeWork
está estrechamente relacionada con la función beginWork
. Mientras que beginWork
es responsable de establecer banderas sobre el estado "debe actualizar" en un nodo de la Fibra,completeWork
es responsable de construir un nuevo árbol que se confirmará en el entorno anfitrión. Cuando completeWork
alcanza la cima y ha construido el nuevo árbol DOM, decimos que "la fase de renderizado ha finalizado". Ahora, React pasa a la fase de confirmación.
La fase de confirmación
La fase de confirmación (ver Figura 4-3) es responsable de actualizar el DOM real con los cambios realizados en el DOM virtual durante la fase de renderizado. Durante la fase de confirmación, el nuevo árbol DOM virtual se confirma en el entorno anfitrión, y el árbol de trabajo en curso se sustituye por el árbol actual. En esta fase también se ejecutan todos los efectos. La fase de confirmación se divide en dos partes: la fase de mutación y la fase de disposición.
La fase de mutación
La fase de mutación es la primera parte de la fase de confirmación, y se encarga de actualizar el DOM real con los cambios realizados en el DOM virtual. Durante esta fase, React identifica las actualizaciones que deben realizarse y llama a una función especial llamadacommitMutationEffects
. Esta función aplica al DOM real las actualizaciones realizadas en los nodos de la Fibra en el árbol alternativo durante la fase de renderizado.
Aquí tienes un ejemplo de pseudocódigo completo de cómo podríaimplementarse commitMutationEffects
:
function
commitMutationEffects
(
Fiber
)
{
switch
(
Fiber
.
tag
)
{
case
HostComponent
:
{
// Update DOM node with new props and/or children
break
;
}
case
HostText
:
{
// Update text content of DOM node
break
;
}
case
ClassComponent
:
{
// Call lifecycle methods like componentDidMount and componentDidUpdate
break
;
}
// ... other cases for different types of nodes
}
}
Durante la fase de mutación, React también llama a otras funciones especiales, como commitUnmount
y commitDeletion
, para eliminar del DOM los nodos que ya no son necesarios.
La fase de diseño
La fase de disposición es la segunda parte de la fase de confirmación, y se encarga de calcular la nueva disposición de los nodos actualizados en el DOM. Durante esta fase, React llama a una función especial llamadacommitLayoutEffects
. Esta función calcula la nueva disposición de los nodos actualizados en el DOM.
Al igual que commitMutationEffects
, commitLayoutEffects
también es una sentencia switch masiva que llama a diferentes funciones, dependiendo del tipo de nodo que se actualice.
Una vez finalizada la fase de maquetación, React ha actualizado correctamente el DOM real para reflejar los cambios realizados en el DOM virtual durante la fase de renderizado.
Al dividir la fase de confirmación en dos partes (mutación y disposición), React es capaz de aplicar actualizaciones al DOM de forma eficiente. Al trabajar conjuntamente con otras funciones clave del reconciliador, la fase de confirmación ayuda a garantizar que las aplicaciones React sean rápidas, receptivas y fiables, incluso a medida que se vuelven más complejas y manejan mayores cantidades de datos.
Efectos
Durante la fase de commit del proceso de reconciliación de React, los efectos secundariosse realizan en un orden específico, dependiendo del tipo de efecto. Hay varios tipos de efectos que pueden producirse durante la fase de commit, entre ellos:
- Efectos de la colocación
-
Estos efectos se producen cuando se añade un nuevo componente al DOM. Por ejemplo, si se añade un nuevo botón a un formulario, se producirá un efecto de colocación para añadir el botón al DOM.
- Efectos de actualización
-
Estos efectos se producen cuando un componente se actualiza con nuevos accesorios o estado. Por ejemplo, si cambia el texto de un botón, se producirá un efecto de actualización para actualizar el texto en el DOM.
- Efectos de supresión
-
Estos efectos se producen cuando se elimina un componente del DOM. Por ejemplo, si se elimina un botón de un formulario, se producirá un efecto de borrado para eliminar el botón del DOM.
- Efectos de disposición
-
Estos efectos se producen antes de que el navegador tenga la oportunidad de pintar, y se utilizan para actualizar el diseño de la página. Los efectos de diseño se gestionan utilizando el gancho
useLayoutEffect
en los componentes de función y el método del ciclo de vidacomponentDidUpdate
en los componentes de clase.
En contraste con estos efectos de fase de confirmación, los efectos pasivos son efectos definidos por el usuario que se programan para ejecutarse después de que el navegador haya tenido la oportunidad de pintar. Los efectos pasivos se gestionan utilizando el gancho useEffect
.
Los efectos pasivos son útiles para realizar acciones que no son críticas para el renderizado inicial de la página, como obtener datos de una API o realizar un seguimiento analítico. Dado que los efectos pasivos no se realizan durante la fase de renderizado, no afectan al tiempo necesario para calcular un conjunto mínimo de actualizaciones necesarias para llevar una interfaz de usuario al estado deseado por el desarrollador.
Poner todo en la pantalla
React mantiene un FiberRootNode
sobre ambos árboles que apunta a uno de los dos árboles: el current
o el workInProgress
. ElFiberRootNode
es una estructura de datos clave que se encarga de gestionar la fase de confirmación del proceso de reconciliación.
Cuando se realizan actualizaciones en el DOM virtual, React actualiza el árbolworkInProgress
, dejando inalterado el árbol actual. Esto permite a React seguir renderizando y actualizando el DOM virtual, al tiempo que preserva el estado actual de laaplicación.
Cuando finaliza el proceso de renderizado, React llama a una función llamadacommitRoot
, que se encarga de transferir los cambios realizados en el árbol workInProgress
al DOM actual. commitRoot
cambia el puntero del FiberRootNode
del árbol actual al árbolworkInProgress
, convirtiendo el árbol workInProgress
en el nuevo árbol actual.
A partir de este momento, cualquier actualización futura se basa en el nuevo árbol actual. Este proceso garantiza que la aplicación permanezca en un estado coherente, y que las actualizaciones se apliquen correcta y eficazmente .
Todo esto parece ocurrir instantáneamente en el navegador. Este es el trabajo de lareconciliación.
Revisión de capítulos
En este capítulo, hemos explorado el concepto de reconciliación de React y hemos aprendido sobre el reconciliador Fibra. También aprendimos sobre las Fibras, que permiten un renderizado eficiente e interrumpible junto con un potente programador. También aprendimos sobre la fase de renderizado y la fase de confirmación, que son las dos fases principales del proceso de reconciliación. Por último, aprendimos sobre FiberRootNode
: una estructura de datos clave responsable de gestionar la fase de confirmación del proceso de reconciliación.
Preguntas de repaso
Hagámonos algunas preguntas para comprobar nuestra comprensión de los conceptos de este capítulo:
-
¿Qué es la reconciliación React?
-
¿Cuál es la función de la estructura de datos Fibra?
-
¿Por qué necesitamos dos árboles?
-
¿Qué ocurre cuando se actualiza una aplicación?
Si podemos responder a estas preguntas, estaremos bien encaminados para comprender el Reconciliador de Fibras y el proceso de reconciliación en React.
Siguiente
En el Capítulo 5, analizaremos preguntas habituales en React y exploraremos algunos patrones avanzados. Responderemos a preguntas sobre con qué frecuencia utilizar useMemo
y cuándo utilizar React.lazy
. También exploraremos cómo utilizaruseReducer
y useContext
para gestionar el estado en las aplicaciones React.
¡Nos vemos allí!
Get React fluido now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.