Cómo convertir componentes de clase React en componentes funcionales con React Hooks

Introducción
La última versión alfa de React introdujo un nuevo concepto llamado Hooks. Los Hooks se introdujeron en React para resolver problemas comunes. Sin embargo, sirven principalmente como una alternativa para las clases. Con Hooks, puedes crear componentes funcionales que usan métodos de estado y ciclo de vida.
Los ganchos están disponibles actualmente en React v16.7.0-alpha. No hay planes de eliminar clases. Los ganchos proporcionan otra forma de escribir React.
Dado que los Hooks son aún nuevos, muchos desarrolladores buscan aplicar el concepto en sus aplicaciones React existentes o en aplicaciones nuevas. En esta publicación, explorará cinco formas de convertir componentes de clase React en componentes funcionales mediante React Hooks.
Prerrequisitos
Para completar este tutorial, necesitarás:
- Familiaridad con JavaScript. Puedes revisar la serie Cómo codificar en JavaScript para obtener más información y comenzar.
- Familiaridad con React. Puedes consultar nuestra serie Cómo codificar en React.js para obtener guías que te ayuden a comenzar.
No se requiere desarrollo local, pero se proporcionan ejemplos de CodeSandbox para una mayor experimentación.
Paso 1: Comprender una clase sin métodos de estado o de ciclo de vida
Comencemos con una clase React que no tiene componentes de estado ni de ciclo de vida:
EjemploClassComponent.js
import React, { Component } from 'react';class App extends Component { alertName = () = { alert('John Doe'); }; render() { return ( div h3This is a Class Component/h3 button onClick={this.alertName} Alert /button /div ); }};export default App;
Aquí tienes una clase React típica, que carece de estado o de un método de ciclo de vida. Alerta a un nombre cuando se hace clic en un botón.
El equivalente funcional de esta clase se verá así:
EjemploFunctionalComponent.js
import React from 'react';function App() { const alertName = () = { alert('John Doe'); }; return ( div h3This is a Functional Component/h3 button onClick={alertName} Alert /button /div );};export default App;
Al igual que el primer ejemplo, esta clase funcional se comporta de manera típica.
Sin embargo, este ejemplo no utiliza Hooks ni nada nuevo todavía. En estos ejemplos, no es necesario el estado ni el ciclo de vida.
Echemos un vistazo a los componentes basados en clases con estado y aprendamos cómo convertirlos en componentes funcionales usando Hooks.
Paso 2: Agregar ganchos a clases con estado
Consideremos una situación en la que tiene una variable de nombre global que puede actualizar dentro de la aplicación desde un campo de entrada de texto.
En React, se manejan casos como este definiendo la variable de nombre en un state
objeto y llamando setState()
cuando tenemos un nuevo valor para actualizar la name
variable con:
EjemploClassComponentWithState.js
import React, { Component } from 'react';class App extends Component { state = { name: '' } alertName = () = { alert(this.state.name); }; handleNameInput = e = { this.setState({ name: e.target.value }); }; render() { return ( div h3This is a Class Component/h3 input type="text" onChange={this.handleNameInput} value={this.state.name} placeholder="Your Name" / button onClick={this.alertName} Alert /button /div ); }}export default App;
Cuando un usuario escribe un nombre en el campo de entrada y hace clic en el botón Alerta , aparece una alerta con el nombre definido en el estado.
Puedes convertir toda esta clase en un componente React funcional usando Hooks:
Ejemplo de componente funcional con estado.js
import React, { useState } from 'react';function App() { const [name, setName] = useState('John Doe'); const alertName = () = { alert(name); }; const handleNameInput = e = { setName(e.target.value); }; return ( div h3This is a Functional Component/h3 input type="text" onChange={handleNameInput} value={name} placeholder="Your Name" / button onClick={alertName} Alert /button /div );};export default App;
Aquí, has presentado el useState
Hook. Te permite hacer uso del estado en los componentes funcionales de React. Con el useState()
Hook, puedes usar el estado en este componente funcional. Utiliza una sintaxis similar con una asignación de desestructuración para matrices.
Considere esta línea:
const [name, setName] = useState('John Doe')
Aquí, name
es el equivalente de this.state
en un componente de clase normal, y setName
es el equivalente de this.setState
.
El valor inicial del estado en el useState()
Hook proviene de un argumento. En otras palabras, el useState()
argumento es el valor inicial del estado. En tu caso, lo estableces en 'John Doe'
. Esto significa que el estado inicial del nombre en el estado es 'John Doe'
.
Este código es un ejemplo de cómo puedes convertir un componente React basado en clases con estado en un componente funcional usando Hooks.
Exploremos otros escenarios, incluidas clases con múltiples propiedades de estado.
Paso 3: Agregar ganchos a clases con múltiples propiedades de estado
Ha visto cómo podría convertir una propiedad de estado con useState
, pero el mismo enfoque no funcionará del todo cuando tenga varias propiedades de estado. Si, por ejemplo, tuviera dos o más campos de entrada para userName
, firstName
y lastName
, entonces tendría un componente basado en clases con tres propiedades de estado:
Ejemplo de componente de clase con múltiples propiedades de estado.js
import React, { Component } from 'react';class App extends Component { state = { userName: '', firstName: '', lastName: '' }; logName = () = { console.log(this.state.userName); console.log(this.state.firstName); console.log(this.state.lastName); }; handleUserNameInput = e = { this.setState({ userName: e.target.value }); }; handleFirstNameInput = e = { this.setState({ firstName: e.target.value }); }; handleLastNameInput = e = { this.setState({ lastName: e.target.value }); }; render() { return ( div h3This is a Class Component/h3 input type="text" onChange={this.handleUserNameInput} value={this.state.userName} placeholder="Your Username" / input type="text" onChange={this.handleFirstNameInput} value={this.state.firstName} placeholder="Your First Name" / input type="text" onChange={this.handleLastNameInput} value={this.state.lastName} placeholder="Your Last Name" / button className="btn btn-large right" onClick={this.logName} Log Names /button /div ); }}export default App;
Para convertir esta clase en un componente funcional con Hooks, deberá tomar una ruta poco convencional. Usando useState()
Hook, el ejemplo anterior se puede escribir como:
Ejemplo de componente funcional con propiedades de estado múltiple.js
import React, { useState } from 'react';function App() { const [userName, setUsername] = useState(''); const [firstName, setFirstname] = useState(''); const [lastName, setLastname] = useState(''); const logName = () = { console.log(userName); console.log(firstName); console.log(lastName); }; const handleUserNameInput = e = { setUsername(e.target.value); }; const handleFirstNameInput = e = { setFirstname(e.target.value); }; const handleLastNameInput = e = { setLastname(e.target.value); }; return ( div h3This is a Functional Component/h3 input type="text" onChange={handleUserNameInput} value={userName} placeholder="Your Username" / input type="text" onChange={handleFirstNameInput} value={firstName} placeholder="Your First Name" / input type="text" onChange={handleLastNameInput} value={lastName} placeholder="Your Last Name" / button className="btn btn-large right" onClick={logName} Log Names /button /div );};export default App;
A continuación se muestra un CodeSandbox para este ejemplo.
Esto demuestra cómo se puede convertir un componente basado en clases con múltiples propiedades de estado en un componente funcional utilizando useState()
Hook.
Paso 4: Agregar ganchos a una clase con estado ycomponentDidMount
Consideremos una clase con state
y componentDidMount
. Para demostrarlo, veremos un escenario en el que se establece un estado inicial para los tres campos de entrada y se hace que todos se actualicen a un conjunto diferente de valores después de cinco segundos.
Para lograr esto, declarará un valor de estado inicial para los campos de entrada e implementará un componentDidMount()
método de ciclo de vida que se ejecutará después de la representación inicial para actualizar los valores de estado:
EjemploClassComponentWithStateAndComponentDidMount.js
import React, { Component } from 'react';class App extends Component { state = { // initial state userName: 'johndoe', firstName: 'John', lastName: 'Doe' } componentDidMount() { setInterval(() = { this.setState({ // update state userName: 'janedoe', firstName: 'Jane', lastName: 'Doe' }); }, 5000); } logName = () = { console.log(this.state.userName); console.log(this.state.firstName); console.log(this.state.lastName); }; handleUserNameInput = e = { this.setState({ userName: e.target.value }); }; handleFirstNameInput = e = { this.setState({ firstName: e.target.value }); }; handleLastNameInput = e = { this.setState({ lastName: e.target.value }); }; render() { return ( div h3This is a Class Component/h3 input type="text" onChange={this.handleUserNameInput} value={this.state.userName} placeholder="Your Username" / input type="text" onChange={this.handleFirstNameInput} value={this.state.firstName} placeholder="Your First Name" / input type="text" onChange={this.handleLastNameInput} value={this.state.lastName} placeholder="Your Last Name" / button className="btn btn-large right" onClick={this.logName} Log Names /button /div ); }}export default App;
Cuando se ejecuta la aplicación, los campos de entrada tendrán los valores iniciales que haya definido en el objeto de estado. Estos valores se actualizarán a los valores que haya definido dentro del componentDidMount()
método después de cinco segundos.
A continuación, convertirá esta clase en un componente funcional utilizando React useState
y useEffect
Hooks:
EjemploFunctionalComponentWithStateAndComponentDidMount.js
import React, { useState, useEffect } from 'react';function App() { const [userName, setUsername] = useState('johndoe'); const [firstName, setFirstname] = useState('John'); const [lastName, setLastname] = useState('Doe'); useEffect(() = { setInterval(() = { setUsername('janedoe'); setFirstname('Jane'); setLastname('Doe'); }, 5000); }); const logName = () = { console.log(userName); console.log(firstName); console.log(lastName); }; const handleUserNameInput = e = { setUsername({ userName: e.target.value }); }; const handleFirstNameInput = e = { setFirstname({ firstName: e.target.value }); }; const handleLastNameInput = e = { setLastname({ lastName: e.target.value }); }; return ( div h3This is a Functional Component/h3 input type="text" onChange={handleUserNameInput} value={userName} placeholder="Your Username" / input type="text" onChange={handleFirstNameInput} value={firstName} placeholder="Your First Name" / input type="text" onChange={handleLastNameInput} value={lastName} placeholder="Your Last Name" / button className="btn btn-large right" onClick={logName} Log Names /button /div );};export default App;
A continuación se muestra un CodeSandbox para este ejemplo.
En términos de funcionalidad, este componente hace exactamente lo mismo que el ejemplo anterior. La única diferencia es que en lugar de utilizar el método de ciclo de vida state
y objeto convencional componentDidMount()
como lo hizo en el componente de clase, utilizó los Hooks useState
y useEffect
.
Paso 5: Agregar ganchos a una clase con estado, componentDidMountycomponentDidUpdate
A continuación, veamos una clase React con estado y dos métodos de ciclo de vida: componentDidMount
y componentDidUpdate
. La mayoría de las soluciones hasta este punto han utilizado useState
Hook. En este ejemplo, nos centraremos en useEffect
Hook.
Para demostrar mejor cómo funciona esto, modifiquemos su código para actualizar dinámicamente el h3
encabezado de la página.
Actualmente, el encabezado dice This is a Class Component
. Ahora, definirá un componentDidMount()
método para actualizar el encabezado para que diga Welcome to React Hooks
después de tres segundos:
Ejemplo de componente de clase con estado y dos métodos de ciclo de vida.js
import React, { Component } from 'react';class App extends Component { state = { header: 'Welcome to React Hooks' } componentDidMount() { const header = document.querySelectorAll('#header')[0]; setTimeout(() = { header.innerHTML = this.state.header; }, 3000); } render() { return ( div h3This is a Class Component/h3 /div ); }}export default App;
Cuando se ejecuta la aplicación, comenzará con el encabezado inicial This is a Class Component
y lo cambiará a Welcome to React Hooks
después de tres segundos. Este es el componentDidMount()
comportamiento clásico, ya que se ejecuta después de que la render
función se ejecuta correctamente.
Agreguemos funcionalidad para actualizar dinámicamente el encabezado desde otro campo de entrada para que el encabezado se actualice con el nuevo texto mientras escribe.
Para lograr esto, deberá implementar el componentDidUpdate()
método de ciclo de vida:
EjemploClassComponent.js
import React, { Component } from 'react';class App extends Component { state = { header: 'Welcome to React Hooks' } componentDidMount() { const header = document.querySelectorAll('#header')[0]; setTimeout(() = { header.innerHTML = this.state.header; }, 3000); } componentDidUpdate() { const node = document.querySelectorAll('#header')[0]; node.innerHTML = this.state.header; } handleHeaderInput = e = { this.setState({ header: e.target.value }); }; render() { return ( div h3This is a Class Component/h3 input type="text" onChange={this.handleHeaderInput} value={this.state.header} / /div ); }}export default App;
Aquí tienes state
, componentDidMount()
, y componentDidUpdate()
. Cuando ejecutas la aplicación, la componentDidMount()
función actualizará el encabezado a Welcome to React Hooks
después de tres segundos. Cuando comienzas a escribir en el campo de entrada de texto del encabezado, el h3
texto se actualizará con el texto de entrada según lo definido en el componentDidUpdate()
método.
A continuación, convertirá esta clase en un componente funcional con el useEffect()
Hook:
Ejemplo de componente funcional con estado y dos métodos de ciclo de vida.js
import React, { useState, useEffect } from 'react';function App() { const [header, setHeader] = useState('Welcome to React Hooks'); useEffect(() = { const newheader = document.querySelectorAll('#header')[0]; setTimeout(() = { newheader.innerHTML = header; }, 3000); }); const handleHeaderInput = e = { setHeader(e.target.value); }; return ( div h3This is a Functional Component/h3 input type="text" onChange={handleHeaderInput} value={header} / /div );};export default App;
Mira este ejemplo en CodeSandbox.
Con este componente lograste la misma funcionalidad que antes con el useEffect()
Hook. También optimizaste el código, ya que no tuviste que escribir código separado para las funciones componentDidMount()
y . Con el Hook, obtienes la funcionalidad de ambos. Esto se debe a que se ejecuta tanto después de la renderización inicial como después de cada actualización posterior de forma predeterminada.componentDidUpdate()
useEffect()
useEffect()
Paso 6 — Conversión PureComponentaReact memo
React PureComponent funciona de manera similar a Component. La principal diferencia entre ellos es que React.Component
no implementa el shouldComponentUpdate()
método de ciclo de vida, mientras que React.PureComponent
sí lo hace.
Si tiene una aplicación donde la render()
función genera el mismo resultado con las mismas propiedades y el mismo estado, puede usarla React.PureComponent
para aumentar el rendimiento en algunos casos.
React.memo()
Funciona de manera similar. Cuando el componente de función muestra el mismo resultado con las mismas propiedades, puedes incluirlo en una llamada a React.memo()
para mejorar el rendimiento. El uso de PureComponent
y React.memo()
proporciona a las aplicaciones React un aumento considerable en el rendimiento, ya que reduce la cantidad de operaciones de renderizado en la aplicación.
Para comprender qué hacen ambos, primero verá el código donde un componente se procesa cada dos segundos, independientemente de si hay o no un cambio en el valor o el estado:
EjemploClassComponent.js
import React, { Component } from 'react';function Unstable(props) { // monitor how many times this component is rendered console.log('Rendered Unstable component'); return ( div p{props.value}/p /div );};class App extends Component { state = { value: 1 }; componentDidMount() { setInterval(() = { this.setState(() = { return { value: 1 }; }); }, 2000); } render() { return ( div Unstable value={this.state.value} / /div ); }}export default App;
Cuando ejecutes la aplicación y revises los registros, notarás que renderiza el componente cada dos segundos, sin ningún cambio en el estado o las propiedades. Esta es una situación que puedes mejorar con PureComponent
y React.memo()
.
La mayoría de las veces, solo desea volver a renderizar un componente cuando haya un cambio de estado o de propiedades. Con el ejemplo anterior, puede mejorarlo PureComponent
para que el componente solo se vuelva a renderizar cuando haya un cambio de estado o de propiedades.
Puedes lograr esto importándolo PureComponent
y ampliándolo:
EjemploPureComponent.js
import React, { PureComponent } from 'react';function Unstable(props) { console.log('Rendered Unstable component'); return ( div p{props.value}/p /div );};class App extends PureComponent { state = { value: 1 }; componentDidMount() { setInterval(() = { this.setState(() = { return { value: 1 }; }); }, 2000); } render() { return ( div Unstable value={this.state.value} / /div ); }}export default App;
Ahora, si vuelves a ejecutar la aplicación, solo obtendrás la representación inicial. No sucede nada más después de eso. Esto se debe a que tienes class App extends PureComponent {}
en lugar de class App extends Component {}
.
Esto resuelve el problema de que los componentes se vuelvan a renderizar sin tener en cuenta el estado actual. Sin embargo, si implementas un cambio de estado dentro de tu setState
método, te encontrarás con otro problema.
Por ejemplo, considere los siguientes cambios en setState()
:
Actualmente, value
configurado en 1
:
componentDidMount() { setInterval(() = { this.setState(() = { return { value: 1 }; }); }, 2000);}
Consideremos una situación en la que value
se establece en Math.random()
:
componentDidMount() { setInterval(() = { this.setState(() = { return { value: Math.round(Math.random()) }; }); }, 2000);}
En este escenario, el primer componente de ejemplo se volvería a renderizar cada vez que el valor se actualizase al siguiente número aleatorio. Sin embargo, PureComponent
permite volver a renderizar los componentes solo cuando se ha producido un cambio en el estado o en las propiedades.
Ahora puedes explorar cómo utilizarlo React.memo()
para lograr la misma solución. Para lograrlo, envuelve el componente con React.memo()
:
EjemploReactMemo.js
import React, { Component } from 'react';const Unstable = React.memo(function Unstable (props) { console.log('Rendered Unstable component'); return ( div p{props.value}/p /div );});class App extends Component { state = { val: 1 }; componentDidMount() { setInterval(() = { this.setState(() = { return { value: 1 }; }); }, 2000); } render() { return ( div Unstable val={this.state.val} / /div ); }}export default App;
Aquí está el CodeSandbox para este ejemplo.
Esto logra el mismo resultado que usar PureComponent
. El componente solo se renderiza después del renderizado inicial y no se vuelve a renderizar hasta que haya un cambio en el estado o en los elementos.
Conclusión
En este tutorial, ha explorado algunos enfoques para convertir un componente basado en clases existente en un componente funcional utilizando React Hooks.
También has visto un caso especial de conversión de una PureComponent
clase React a React.memo()
.
Para usar Hooks en tus aplicaciones, asegúrate de actualizar tu versión de React a la versión compatible:
"react": "^16.7.0-alpha","react-dom": "^16.7.0-alpha",
Ahora tienes una base para experimentar más con React Hooks.
Obtén más información sobre cómo comenzar a usar React Hooks y cómo crear una aplicación de tareas pendientes de React con React Hooks.
Deja una respuesta