驴En qu茅 se diferencian los componentes funcionales de React de los componentes basados 鈥嬧媏n clases?

驴En qu茅 se diferencian los componentes funcionales de React de los componentes basados 鈥嬧媏n 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 鈥嬧媏n 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 鈥嬧媏n 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 equivalentes

Sin 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 鈥嬧媏n la clase, cuyo c贸digo se muestra arriba.

Intente, para cada uno de estos botones, realizar la siguiente secuencia de acciones:

  1. Haga clic en el bot贸n
  2. Cambie el perfil seleccionado antes de que transcurran 3 segundos despu茅s de hacer clic en el bot贸n.
  3. 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 鈥嬧媏n clases

En 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 鈥嬧媏n 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 鈥嬧媏n 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 鈥嬧媏n 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() {   //  !   const props = this.props;   //    ,      render.   //   -   .   const showMessage = () => {     alert('Followed ' + props.user);   };   const handleClick = () => {     setTimeout(showMessage, 3000);   };   return <button onClick={handleClick}>Follow</button>; } } 

Como puede ver, aqu铆 "capturamos" las propiedades durante la llamada al m茅todo de render .


Propiedades capturadas por la llamada de render

Con 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 funcional

Este 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 鈥嬧媏n 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); //     `ref.current`. // ... } 

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(''); //    . const latestMessage = useRef(''); useEffect(() => {   latestMessage.current = message; }); const showMessage = () => {   alert('You said: ' + latestMessage.current); }; 

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 鈥嬧媏n 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 鈥

Source: https://habr.com/ru/post/444348/


All Articles