¿En qué se diferencian los componentes funcionales de React de los componentes basados en clases? Desde hace bastante tiempo, la respuesta tradicional a esta pregunta es: "El uso de clases le permite utilizar una gran cantidad de características de componentes, por ejemplo, el estado". Ahora, con la llegada de los
ganchos , esta respuesta ya no refleja el verdadero estado de cosas.
Es posible que haya escuchado que uno de estos tipos de componentes tiene un mejor rendimiento que el otro. Pero cual? La mayoría de los puntos de referencia que prueban esto tienen
fallas , por lo que
sacaría conclusiones basadas en sus resultados con gran precaución. El rendimiento depende principalmente de lo que sucede en el código, y no de si los componentes funcionales o los componentes basados en clases se eligen para implementar ciertas capacidades. Nuestro estudio mostró que la diferencia en el rendimiento entre los diferentes tipos de componentes es insignificante. Sin embargo, debe tenerse en cuenta que las estrategias de optimización utilizadas para trabajar con ellos
difieren ligeramente.

En cualquier caso,
no recomiendo reescribir los componentes existentes usando nuevas tecnologías si no hay buenas razones para ello, y si no le importa estar entre aquellos que comenzaron a usar estas tecnologías antes que los demás. Los ganchos siguen siendo una tecnología nueva (igual que la biblioteca React en 2014), y algunas "mejores prácticas" para su aplicación aún no se han incluido en los manuales de React.
¿A dónde llegamos finalmente? ¿Existen diferencias fundamentales entre los componentes funcionales de React y los componentes basados en clases? Por supuesto, hay tales diferencias. Estas son diferencias en el modelo mental de usar tales componentes. En este artículo consideraré su diferencia más seria. Ha existido desde que, en 2015, aparecieron los componentes funcionales, pero a menudo se pasa por alto. Consiste en el hecho de que los componentes funcionales capturan valores representados. Hablemos de lo que eso realmente significa.
Cabe señalar que este material no constituye un intento de evaluar componentes de diferentes tipos. Solo describo la diferencia entre los dos modelos de programación en React. Si desea saber más sobre el uso de componentes funcionales a la luz de las innovaciones, consulte
esta lista de preguntas y respuestas sobre ganchos.
¿Cuáles son las características del código de componentes basado en funciones y clases?
Considere este componente:
function ProfilePage(props) { const showMessage = () => { alert('Followed ' + props.user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ( <button onClick={handleClick}>Follow</button> ); }
Muestra un botón que, al presionar la función
setTimeout
, simula una solicitud de red y luego muestra un cuadro de mensaje que confirma la operación. Por ejemplo, si '
props.user
'Dan'
está almacenado en
props.user
, en la ventana del mensaje, después de tres segundos, se mostrará
'Followed Dan'
.
Tenga en cuenta que no importa si las funciones de flecha o las declaraciones de función se usan aquí. Una construcción de la
function handleClick()
formulario
function handleClick()
funcionará exactamente de la misma manera.
¿Cómo reescribir este componente como una clase? Si solo rehace el código que acaba de examinar y lo convierte al código de un componente basado en una clase, obtendrá lo siguiente:
class ProfilePage extends React.Component { showMessage = () => { alert('Followed ' + this.props.user); }; handleClick = () => { setTimeout(this.showMessage, 3000); }; render() { return <button onClick={this.handleClick}>Follow</button>; } }
En general, se acepta que dos de esos fragmentos de código son equivalentes. Y los desarrolladores a menudo son completamente libres, en el curso de la refactorización de código, se transforman uno en otro, sin pensar en las posibles consecuencias.
Estas piezas de código parecen ser equivalentesSin embargo, existe una ligera diferencia entre estos fragmentos de código. Míralos más de cerca. ¿Ves la diferencia? Por ejemplo, no la vi de inmediato.
Además, consideraremos esta diferencia, por lo tanto, para aquellos que quieran comprender la esencia de lo que está sucediendo ellos mismos, un ejemplo práctico de este código.
Antes de continuar, me gustaría enfatizar que la diferencia en cuestión no tiene nada que ver con los ganchos React. En los ejemplos anteriores, por cierto, los ganchos ni siquiera se usan. Se trata de la diferencia entre funciones y clases en React. Y si planea usar muchos componentes funcionales en sus aplicaciones React, entonces es posible que desee comprender esta diferencia.
De hecho, ilustraremos la diferencia entre funciones y clases con el ejemplo de un error que a menudo se encuentra en las aplicaciones React.
El error que es común en las aplicaciones React.
Abra
la página de ejemplo que muestra una lista que le permite seleccionar perfiles de usuario y dos botones
Follow
que se muestran mediante los
ProfilePageClass
ProfilePageFunction
y
ProfilePageClass
, funcionales y basados en la clase, cuyo código se muestra arriba.
Intente, para cada uno de estos botones, realizar la siguiente secuencia de acciones:
- Haga clic en el botón
- Cambie el perfil seleccionado antes de que transcurran 3 segundos después de hacer clic en el botón.
- Lea el texto que se muestra en el cuadro de mensaje.
Una vez hecho esto, notará las siguientes características:
- Cuando hace clic en el botón formado por el componente funcional con el perfil de
Dan
seleccionado y luego cambia al perfil de Sophie
, se mostrará 'Followed Dan'
en el cuadro de mensaje. - Si hace lo mismo con un botón formado por un componente basado en una clase, se mostrará
'Followed Sophie'
.
Características de componentes basados en clasesEn este ejemplo, el comportamiento del componente funcional es correcto. Si me suscribí al perfil de alguien y luego cambié a otro perfil, mi componente no debería dudar de a qué perfil me suscribí. Obviamente, la implementación del mecanismo en cuestión basado en el uso de clases contiene un error (por cierto, definitivamente debes suscribirte a
Sofia ).
Causas del mal funcionamiento de componentes basados en clases
¿Por qué un componente basado en clases se comporta de esta manera? Para entender esto, echemos un vistazo al método
showMessage
en nuestra clase:
class ProfilePage extends React.Component { showMessage = () => { alert('Followed ' + this.props.user); };
Este método lee datos de
this.props.user
. Las propiedades en React son inmutables, por lo que no cambian. Sin embargo,
this
, como siempre, es una entidad mutable.
De hecho, el propósito de tener
this
en una clase radica en la capacidad de
this
para cambiar. La propia biblioteca React realiza periódicamente
this
mutaciones, lo que permite trabajar con las últimas versiones del método de
render
y los métodos del ciclo de vida de los componentes.
Como resultado, si nuestro componente se vuelve a representar durante la ejecución de la solicitud,
this.props
cambiará. Después de eso, el método
showMessage
leerá el valor del
user
de la entidad de
props
"demasiado nueva".
Esto le permite hacer una observación interesante con respecto a las interfaces de usuario. Si decimos que la interfaz de usuario, conceptualmente, es una función del estado actual de la aplicación, entonces los controladores de eventos son parte de los resultados de representación, al igual que los resultados de representación visibles. Nuestros controladores de eventos "pertenecen" a una operación de representación específica junto con propiedades y estado específicos.
Sin embargo, la programación de un tiempo de espera cuya devolución de llamada de
this.props
lee viola esta conexión. La devolución de
showMessage
showMessage no
showMessage
"vinculada" a ninguna operación de representación en particular, como resultado, "pierde" las propiedades correctas. Leer datos de
this
rompe esta conexión.
¿Cómo, por medio de componentes basados en clases, resolver el problema?
Imagine que no hay componentes funcionales en React. ¿Cómo entonces resolver este problema?
Necesitamos algún mecanismo para "restaurar" la conexión entre el método de
render
con las propiedades correctas y la devolución de
showMessage
showMessage, que lee los datos de las propiedades. Este mecanismo debe ubicarse en algún lugar donde se pierda la esencia de los
props
con los datos correctos.
Una forma de hacerlo es leer
this.props
de antemano en el controlador de eventos y luego pasar explícitamente lo que se leyó a la función de devolución de llamada utilizada en
setTimeout
:
class ProfilePage extends React.Component { showMessage = (user) => { alert('Followed ' + user); }; handleClick = () => { const {user} = this.props; setTimeout(() => this.showMessage(user), 3000); }; render() { return <button onClick={this.handleClick}>Follow</button>; } }
Este enfoque está
funcionando . Pero las construcciones adicionales utilizadas aquí, con el tiempo, conducirán a un aumento en el volumen del código y al hecho de que aumentará la probabilidad de errores en él. ¿Qué pasa si necesitamos más de una sola propiedad? ¿Qué pasa si también necesitamos trabajar con el estado? Si el método
showMessage
otro método y este método lee
this.props.something
o
this.state.something
, entonces volveremos a encontrar el mismo problema. Y para resolverlo, tendríamos que pasar
this.props
y
this.state
como argumentos a todos los métodos llamados desde
showMessage
.
Si es verdad hacerlo, destruirá todas las comodidades que ofrece el uso de componentes basados en clases. Es difícil recordar que trabajar con métodos de esta manera es difícil, es difícil automatizar, como resultado, los desarrolladores a menudo, en lugar de usar métodos similares, están de acuerdo en que hay errores en sus proyectos.
Del mismo modo, incrustar el código de
alert
en
handleClick
no resuelve un problema más global. Necesitamos estructurar el código para que pueda dividirse en muchos métodos, pero también para que podamos leer las propiedades y el estado que corresponden a la operación de representación asociada con una llamada en particular. Este problema, por cierto, ni siquiera se aplica exclusivamente a React. Puede reproducirlo en cualquier biblioteca para desarrollar interfaces de usuario, que coloca datos en objetos mutables como
this
.
¿Quizás para resolver este problema, puede vincular métodos a
this
en el constructor?
class ProfilePage extends React.Component { constructor(props) { super(props); this.showMessage = this.showMessage.bind(this); this.handleClick = this.handleClick.bind(this); } showMessage() { alert('Followed ' + this.props.user); } handleClick() { setTimeout(this.showMessage, 3000); } render() { return <button onClick={this.handleClick}>Follow</button>; } }
Pero esto no resuelve nuestro problema. Recuerde que es que estamos leyendo datos de
this.props
demasiado tarde, ¡y no en la sintaxis utilizada! Sin embargo, este problema se resolverá si confiamos en los cierres de JavaScript.
Los desarrolladores a menudo intentan evitar cierres, ya que
no es
fácil pensar en valores que, con el tiempo, no pueden mutar. ¡Pero las propiedades en React son inmutables! (O, como mínimo, esto es muy recomendable). Esto le permite dejar de percibir los cierres como algo por lo que el programador puede, como dicen, "pegarse un tiro en el pie".
Esto significa que si "bloquea" las propiedades o el estado de una operación de representación en particular en el cierre, siempre puede contar con que no cambien.
class ProfilePage extends React.Component { render() {
Como puede ver, aquí "capturamos" las propiedades durante la llamada al método de
render
.
Propiedades capturadas por la llamada de renderCon este enfoque, se garantiza que cualquier código que se encuentre en el método de
render
(incluido
showMessage
) verá las propiedades capturadas durante una llamada particular a este método. Como resultado, React ya no podrá evitar que hagamos lo que necesitamos.
En el método de
render
, puede describir tantas funciones auxiliares como desee y todas ellas podrán usar las propiedades y el estado "capturados". Así es como los cierres resolvieron nuestro problema.
Análisis de la solución del problema mediante cierre.
Lo que acabamos de llegar nos permite
resolver el problema , pero ese código parece extraño. ¿Por qué se necesita una clase si las funciones se declaran dentro del método de
render
y no como métodos de clase?
De hecho, podemos simplificar este código deshaciéndonos del "shell" en la forma de una clase que lo rodea:
function ProfilePage(props) { const showMessage = () => { alert('Followed ' + props.user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ( <button onClick={handleClick}>Follow</button> ); }
Aquí, como en el ejemplo anterior, las propiedades se capturan en la función, ya que React se las pasa como un argumento. A diferencia de
this
, React nunca
props
objeto de
props
.
Esto se vuelve un poco más obvio si los
props
destruyen en la declaración de función:
function ProfilePage({ user }) { const showMessage = () => { alert('Followed ' + user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ( <button onClick={handleClick}>Follow</button> ); }
Cuando el componente principal
ProfilePage
con otras propiedades, React volverá a llamar a la función
ProfilePage
. Pero el controlador de eventos que ya se ha llamado "pertenece" a la llamada anterior a esta función, esta llamada usa su propio valor de
user
y su propia devolución de
showMessage
showMessage, que lee este valor. Todo esto permanece intacto.
Es por eso que en la versión
original de nuestro ejemplo, cuando se trabaja con un componente funcional, seleccionar otro perfil después de hacer clic en el botón correspondiente antes de que se muestre el mensaje no cambia nada. Si se seleccionó un perfil de
Sophie
antes de hacer clic en el botón, se mostrará
'Followed Sophie'
en la ventana del mensaje, pase lo que pase.
Usando un componente funcionalEste comportamiento es correcto (por cierto, también puede inscribirse en
Sunil ).
Ahora hemos descubierto cuál es la gran diferencia entre funciones y clases en React. Como ya se mencionó, estamos hablando del hecho de que los componentes funcionales capturan valores. Ahora hablemos de ganchos.
Ganchos
Cuando se usan ganchos, el principio de "capturar valores" se extiende al estado. Considere el siguiente ejemplo:
function MessageThread() { const [message, setMessage] = useState(''); const showMessage = () => { alert('You said: ' + message); }; const handleSendClick = () => { setTimeout(showMessage, 3000); }; const handleMessageChange = (e) => { setMessage(e.target.value); }; return ( <> <input value={message} onChange={handleMessageChange} /> <button onClick={handleSendClick}>Send</button> </> ); }
→
Aquí puedes experimentar con él
Aunque este no es un ejemplo ejemplar de una interfaz de aplicación de mensajería, este proyecto ilustra la misma idea: si un usuario envió un mensaje, el componente no debe confundirse acerca de qué mensaje se envió. El
message
constante de este componente funcional captura el estado que "pertenece" al componente que hace que el navegador sea el controlador de clics para el botón que llama. Como resultado, el
message
almacena lo que estaba en el campo de entrada al momento de hacer clic en el botón
Send
.
El problema de capturar propiedades y estados por componentes funcionales
Sabemos que los componentes funcionales en React, por defecto, capturan propiedades y estados. Pero, ¿qué sucede si necesitamos leer los últimos datos de propiedades o estados que no pertenecen a una llamada de función en particular? ¿Qué pasa si queremos "
leerlos del futuro "?
En componentes basados en clases, esto podría hacerse simplemente haciendo referencia a
this.props
o
this.state
, ya que
this
es una entidad mutable. Su cambio se dedica a Reaccionar. Los componentes funcionales también pueden funcionar con valores mutables que comparten todos los componentes. Estos valores se denominan
ref
:
function MyComponent() { const ref = useRef(null);
Sin embargo, el programador necesita gestionar dichos valores de forma independiente.
La esencia de
ref
juega el mismo
papel que los campos de una instancia de una clase. Esta es una "salida de emergencia" en un mundo imperativo mutable. Puede que esté familiarizado con el concepto de referencias DOM, pero esta idea es mucho más general. Se puede comparar con una caja en la que un programador puede poner algo.
Incluso externamente, una construcción de la forma
this.something
parece una imagen especular de la construcción de
this.something
. Son una representación del mismo concepto.
De forma predeterminada, React no crea entidades de
ref
en los componentes funcionales para los valores de propiedad o estado más recientes. En muchos casos, no los necesitará, y su creación automática sería una pérdida de tiempo. Sin embargo, trabajar con ellos, si es necesario, se puede organizar por su cuenta:
function MessageThread() { const [message, setMessage] = useState(''); const latestMessage = useRef(''); const showMessage = () => { alert('You said: ' + latestMessage.current); }; const handleSendClick = () => { setTimeout(showMessage, 3000); }; const handleMessageChange = (e) => { setMessage(e.target.value); latestMessage.current = e.target.value; };
Si leemos el
message
en
showMessage
, veremos el mensaje que estaba en el campo al momento de hacer clic en el botón
Send
. Pero si lee
latestMessage.current
, puede obtener el último valor, incluso si continuamos ingresando texto en el campo después de hacer clic en el botón
Send
.
Puede comparar
este y
estos ejemplos para evaluar independientemente la diferencia. El valor de
ref
es una forma de "evitar" la uniformidad del renderizado, en algunos casos puede ser muy útil.
En general, debe evitar leer o escribir valores de
ref
durante el proceso de representación porque estos valores son mutables. Nos esforzamos por hacer que la representación sea predecible. Sin embargo, si necesitamos obtener el valor más reciente de algo almacenado en propiedades o en estado, actualizar manualmente el valor de
ref
puede ser una tarea tediosa. Se puede automatizar utilizando el efecto:
function MessageThread() { const [message, setMessage] = useState('');
→
Aquí hay un ejemplo que usa este código
Estamos asignando un valor dentro del efecto, como resultado, el valor de
ref
solo cambiará después de que se actualice el DOM. Esto asegura que nuestra mutación no interrumpa características como
Time Slicing y Suspense , que dependen de la continuidad de las operaciones de renderizado.
Usar el valor de
ref
de esta manera no se requiere a menudo. La captura de propiedades o estados generalmente parece ser un patrón mucho mejor del comportamiento estándar del sistema. Sin embargo, esto puede ser conveniente cuando se trabaja con
API imperativas , como las que usan intervalos o suscripciones. Recuerde que puede trabajar de esta manera con cualquier valor: con propiedades, con variables almacenadas en estado, con todo el objeto de
props
o incluso con una función.
Este patrón, además, puede ser útil para fines de optimización. Por ejemplo, cuando algo como
useCallback
cambia con demasiada frecuencia. Es cierto que la
solución preferida a menudo
es usar un reductor .
Resumen
En este artículo, observamos uno de los patrones incorrectos para usar componentes basados en clases y hablamos sobre cómo resolver este problema con los cierres. Sin embargo, puede notar que cuando intenta optimizar los enlaces especificando una serie de dependencias, puede encontrar errores relacionados con cierres obsoletos. ¿Significa esto que las fallas en sí mismas son un problema? No lo creo
Como se muestra arriba, los cierres, de hecho, nos ayudan a solucionar pequeños problemas que son difíciles de notar. Asimismo, facilitan la escritura de código que funciona correctamente en
paralelo . Esto es posible debido al hecho de que dentro del componente las propiedades y el estado correctos con los que se procesó este componente están "bloqueados".
En todos los casos que he visto hasta ahora, el problema de los "cierres obsoletos" se produjo debido a la suposición errónea de que "las funciones no cambian", o que "las propiedades siempre son las mismas". Espero que después de leer este material, esté convencido de que esto no es así.
Las funciones "capturan" sus propiedades y estado, y por lo tanto, también es importante comprender qué funciones están en cuestión. Esto no es un error; es una característica de los componentes funcionales. Las funciones no deben excluirse de la "matriz de dependencias" para
useEffect
o
useCalback
, por ejemplo. (Una herramienta adecuada para resolver el problema suele ser
useReducer
o
useRef
. Hablamos de esto anteriormente, y pronto prepararemos materiales que se dedicarán a la elección de este o aquel enfoque).
Si la mayor parte del código en nuestras aplicaciones se basará en componentes funcionales, esto significa que necesitamos saber más sobre la
optimización del código y qué valores pueden
cambiar con el tiempo.
: « , , , , , ».
. , React , . , « », . , React .
, , .
React —