Componentes funcionales con ganchos de reacción. ¿Por qué son mejores?

Relativamente recientemente, se lanzó la versión 16.8 de React.js, con la cual los ganchos estuvieron disponibles para nosotros. El concepto de ganchos le permite escribir componentes funcionales completos utilizando todas las características de React, y le permite hacer esto de muchas maneras más convenientemente que lo que hicimos nosotros usando clases.


Muchos percibieron la aparición de ganchos con críticas, y en este artículo me gustaría hablar sobre algunas ventajas importantes que nos brindan los componentes funcionales con ganchos, y por qué deberíamos cambiarlos.


No profundizaré deliberadamente en los detalles del uso de ganchos. Esto no es muy importante para comprender los ejemplos en este artículo; una comprensión general de React es suficiente. Si desea leer exactamente sobre este tema, la información sobre ganchos está en la documentación , y si este tema es interesante, escribiré un artículo con más detalle sobre cuándo, qué y cómo usar los ganchos correctamente.


Los ganchos facilitan la reutilización del código


Imaginemos un componente que representa una forma simple. Algo que simplemente generará algunas entradas y nos permitirá editarlas.


Algo así, si se simplifica enormemente, este componente se vería como una clase:


class Form extends React.Component { state = { //   fields: {}, }; render() { return ( <form> {/*    */} </form> ); }; } 

Ahora imagine que queremos guardar automáticamente los valores de campo cuando cambian. Sugiero omitir declaraciones de funciones adicionales, como shallowEqual y debounce .


 class Form extends React.Component { constructor(props) { super(props); this.saveToDraft = debounce(500, this.saveToDraft); }; state = { //   fields: {}, // ,       draft: { isSaving: false, lastSaved: null, }, }; saveToDraft = (data) => { if (this.state.isSaving) { return; } this.setState({ isSaving: true, }); makeSomeAPICall().then(() => { this.setState({ isSaving: false, lastSaved: new Date(), }) }); } componentDidUpdate(prevProps, prevState) { if (!shallowEqual(prevState.fields, this.state.fields)) { this.saveToDraft(this.state.fields); } } render() { return ( <form> {/*    ,     */} {/*    */} </form> ); }; } 

Mismo ejemplo, pero con ganchos:


 const Form = () => { //     const [fields, setFields] = useState({}); const [draftIsSaving, setDraftIsSaving] = useState(false); const [draftLastSaved, setDraftLastSaved] = useState(false); useEffect(() => { const id = setTimeout(() => { if (draftIsSaving) { return; } setDraftIsSaving(true); makeSomeAPICall().then(() => { setDraftIsSaving(false); setDraftLastSaved(new Date()); }); }, 500); return () => clearTimeout(id); }, [fields]); return ( <form> {/*    ,     */} {/*    */} </form> ); } 

Como vemos, la diferencia aún no es muy grande. Cambiamos el useState y hacer que el guardado en el borrador no esté en componentDidUpdate , sino después de representar el componente usando el useEffect .


La diferencia que quiero mostrar aquí (hay otras, discutiremos a continuación): podemos obtener este código y usarlo en otro lugar:


 //  useDraft       const useDraft = (fields) => { const [draftIsSaving, setDraftIsSaving] = useState(false); const [draftLastSaved, setDraftLastSaved] = useState(false); useEffect(() => { const id = setTimeout(() => { if (draftIsSaving) { return; } setDraftIsSaving(true); makeSomeAPICall().then(() => { setDraftIsSaving(false); setDraftLastSaved(new Date()); }); }, 500); return () => clearTimeout(id); }, [fields]); return [draftIsSaving, draftLastSaved]; } const Form = () => { //     const [fields, setFields] = useState({}); const [draftIsSaving, draftLastSaved] = useDraft(fields); return ( <form> {/*    ,     */} {/*    */} </form> ); } 

¡Ahora podemos usar el gancho useDraft que acabamos de escribir en otros componentes! Esto, por supuesto, es un ejemplo muy simplificado, pero reutilizar la misma funcionalidad es una característica muy útil.


Los ganchos le permiten escribir código más intuitivo.


Imagine un componente (hasta ahora en forma de clase), que, por ejemplo, muestra una ventana del chat actual, una lista de posibles destinatarios y un formulario para enviar un mensaje. Algo como esto:


 class ChatApp extends React.Component { state = { currentChat: null, }; handleSubmit = (messageData) => { makeSomeAPICall(SEND_URL, messageData) .then(() => { alert(`   ${this.state.currentChat} `); }); }; render() { return ( <Fragment> <ChatsList changeChat={currentChat => { this.setState({ currentChat }); }} /> <CurrentChat id={currentChat} /> <MessageForm onSubmit={this.handleSubmit} /> </Fragment> ); }; } 

El ejemplo es muy condicional, pero es bastante adecuado para la demostración. Imagine estas acciones del usuario:


  • Chat abierto 1
  • Enviar un mensaje (imagine que la solicitud lleva mucho tiempo)
  • Abrir chat 2
  • Recibir mensaje sobre envío exitoso:
    • "Mensaje de chat enviado 2"

¿Pero el mensaje fue enviado al chat 1? Esto sucedió debido al hecho de que el método de clase no funcionaba con el valor que estaba en el momento del envío, sino con el que ya estaba en el momento en que se completó la solicitud. Esto no sería un problema en un caso tan simple, pero la corrección de tal comportamiento requerirá atención adicional y procesamiento adicional, y en segundo lugar, puede ser una fuente de errores.


En el caso de un componente funcional, el comportamiento es diferente:


 const ChatApp = () => { const [currentChat, setCurrentChat] = useState(null); const handleSubmit = useCallback( (messageData) => { makeSomeAPICall(SEND_URL, messageData) .then(() => { alert(`   ${currentChat} `); }); }, [currentChat] ); render() { return ( <Fragment> <ChatsList changeChat={setCurrentChat} /> <CurrentChat id={currentChat} /> <MessageForm onSubmit={handleSubmit} /> </Fragment> ); }; } 

Imagine las mismas acciones del usuario:


  • Chat abierto 1
  • Enviar un mensaje (la solicitud tarda mucho tiempo nuevamente)
  • Abrir chat 2
  • Recibir mensaje sobre envío exitoso:
    • "Mensaje de chat enviado 1"

Entonces, ¿qué ha cambiado? Lo que ha cambiado es que ahora para cada render para el que currentChat es diferente, estamos creando un nuevo método. Esto nos permite no pensar en absoluto si algo cambiará en el futuro: estamos trabajando con lo que tenemos ahora . Cada componente de render cierra en sí mismo todo lo que se relaciona con él .


Los ganchos nos salvan del ciclo de vida


Este elemento se superpone con el anterior. React es una biblioteca para describir declarativamente una interfaz. La declarabilidad facilita enormemente la escritura y el soporte de componentes, le permite pensar menos sobre lo que habría que hacer imperativamente si no hubiéramos usado React.


A pesar de esto, cuando usamos clases, nos enfrentamos con el ciclo de vida del componente. Si no profundiza, se ve así:


  • Montaje de componentes
  • Actualización de componentes (al cambiar de state o props )
  • Remoción de componentes

Parece conveniente, pero estoy convencido de que es conveniente únicamente por el hábito. Este enfoque no es como Reaccionar.


En cambio, los componentes funcionales con ganchos nos permiten escribir componentes, pensando no en el ciclo de vida, sino en la sincronización . Escribimos la función para que su resultado refleje de manera única el estado de la interfaz dependiendo de los parámetros externos y el estado interno.


El useEffect , que muchos perciben como un reemplazo directo de componentDidMount , componentDidUpdate etc., en realidad está destinado a otro. Cuando lo usamos, de alguna manera decimos la reacción: "Después de renderizar esto, realice estos efectos".


Aquí hay un buen ejemplo de cómo funciona el componente con el contador de clics de un artículo grande sobre useEffect :


  • Reaccionar: dime qué hacer con este estado.
  • Su componente:
    • Aquí está el resultado del renderizado: <p> 0 </p> .
    • Y, por favor, realice este efecto cuando termine: () => { document.title = ' 0 ' } .
  • Reaccionar: está bien. Actualización de la interfaz. Hola, navegador, estoy actualizando el DOM
  • Navegador: Genial, dibujé.
  • Reaccionar: Super, ahora llamaré al efecto que recibí del componente.
    • Comienza () => { document.title = ' 0 ' }

Mucho más declarativo, ¿no?


Resumen


React Hooks nos permite deshacernos de algunos problemas y facilitar la comprensión y la codificación de componentes. Solo necesita cambiar el modelo mental que les aplicamos. Los componentes funcionales son esencialmente funciones de interfaz de parámetros. Describen todo como debe ser en un momento dado y ayudan a no pensar en cómo responder a los cambios.


Sí, a veces necesita aprender cómo usarlos correctamente , pero de la misma manera, no aprendimos a usar componentes en forma de clases de inmediato.

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


All Articles