Comparación de herramientas de estado de React: Mutative, Immer y reductores
Al trabajar con estados en React, disponemos de varias opciones para gestionarlos y modificarlos en nuestros proyectos. Este artículo analizará tres soluciones para gestionar estados inmutables: Reducer, Immer y Mutative.
Ofreceremos una descripción general de cada herramienta con ejemplos de su uso. Luego, compararemos su rendimiento y determinaremos cuál es mejor para la gestión del estado.
Empecemos.
Descripción general de los reductores de React
La mayoría de los desarrolladores de React ya están familiarizados con las funciones reductoras. Sin embargo, vamos a repasarlas brevemente y de forma sencilla.
Las mejores herramientas para implementar la búsqueda de comercio electrónico en ReactLos reductores nos ayudan a modificar estados dentro de tipos de acciones específicas de manera más elegante ya limpiar nuestro código si necesitamos administrar estados complejos dentro de estos tipos.
Por ejemplo, consideramos una aplicación simple de tareas pendientes. Sin reductores, usaríamos useState
para su gestión de estado. Tendría funciones como addTodoItem
, removeTodoItem
, y markComplete
.
Veamos esto en el código:
importar {useState} de 'react'; importar cn de 'nombres de clase'; interfaz TodoItemProps { título : cadena ; está completo: booleano; } función TodoExample () { const [ todos , setTodos ] = useState TodoItemProps []([]); const [inputText, setInputText] = useState cadena (''); const addTodoItem = ( título : cadena ) = { setTodos (( todo ) = [... todo , { título , isComplete : false }]); }; const removeTodoItem = ( id : numero ) = { setTodos (( todo ) = todo . filter (( _ , ind ) = ind !== id )); }; const markComplete = ( id : número ) = { setTodos (( todo ) = { const nuevoTodo = [... todo ]; nuevoTodo [ id ]. isComplete = true ; return nuevoTodo ; }); }; devolver ( div className = "max-w-[400px] w-full mx-auto my-10" div className = "flex items-center gap-4 mb-4" entrada placeholder = "Ingrese un nuevo elemento de tarea" className = "borde redondeado-md w-completo p-1" valor ={ inputText } onChange ={( e ) = setInputText ( e . target . value )} / button className = "bg-gray-200 w-[200px] py-1 rounded-md" onClick ={() = { addTodoItem ( entradaTexto ); setInputText ( ' ' }} Agregar Todo / botón /div div className = "espacio-y-4" { todos.map ( ( elemento , ind ) = ( div clave ={ ind } className = "flex elementos-centro espacio-4 " h3 nombre de clase ={ cn ( elemento . ¿Está completo? 'texto gris-600' : 'texto gris-900' , 'texto pequeño flex-1' )} { item . title } / h3 botón className = "bg-gray-200 px-2 py-1 rounded-md" onClick ={() = markComplete ( ind )} Marcar como completado / button button className = "bg-gray-200 px-2 py-1 rounded-md" onClick = {() = removeTodoItem ( ind )} Eliminar / botón /div ) ) } /div /div ) ; } exportar predeterminado TodoExample ;
Podemos mover la todo
lógica en un Hook personalizado para que el código sea más claro. Creemos un nuevo useTodo
Hook y movemos nuestra lógica aquí:
importar {useState} de 'react'; interfaz de exportación TodoItemProps { título : cadena ; está completo: booleano; } exportar const useTodo = () = { const [ todos , setTodos ] = useState TodoItemProps []([]); const addTodoItem = ( título : cadena ) = { setTodos (( todo ) = [... todo , { título , isComplete : false }]); }; const removeTodoItem = ( id : numero ) = { setTodos (( todo ) = todo . filter (( _ , ind ) = ind !== id )); }; const markComplete = ( id : número ) = { setTodos (( todo ) = { const nuevoTodo = [... todo ]; nuevoTodo [ id ]. isComplete = true ; return nuevoTodo ; }); }; return {todos, addTodoItem, removeTodoItem, markComplete}; };
Luego, podemos modificar nuestro componente, como se muestra a continuación. Esto ayudará a que el código sea mucho más claro:
importar {useState} de 'react'; importar cn de 'nombres de clase'; importar { useTodo } de './hooks/useTodo' ; función TodoExample() { const { addTodoItem , markComplete , removeTodoItem , todos } = useTodo(); const [ inputText , setInputText ] = useState cadena ( '' ); devolver ( div className = "max-w-[400px] w-full mx-auto my-10" div className = "flex items-center gap-4 mb-4" entrada placeholder = "Ingrese un nuevo elemento de tarea" className = "borde redondeado-md w-completo p-1" valor ={ inputText } onChange ={( e ) = setInputText ( e . target . value )} / button className = "bg-gray-200 w-[200px] py-1 rounded-md" onClick ={() = { addTodoItem ( entradaTexto ); setInputText ( ' ' }} Agregar Todo / botón /div div className = "espacio-y-4" { todos.map ( ( elemento , ind ) = ( div clave ={ ind } className = "flex elementos-centro espacio-4 " h3 nombre de clase ={ cn ( elemento . ¿Está completo? 'texto gris-600' : 'texto gris-900' , 'texto pequeño flex-1' )} { item . title } / h3 botón className = "bg-gray-200 px-2 py-1 rounded-md" onClick ={() = markComplete ( ind )} Marcar como completado / button button className = "bg-gray-200 px-2 py-1 rounded-md" onClick = {() = removeTodoItem ( ind )} Eliminar / botón /div ) ) } /div /div ) ; } exportar predeterminado TodoExample ;
Este ejemplo es adecuado para nuestra aplicación. El useState
Hook es útil para gestionar estados simples o individuales.
Sin embargo, si nuestros objetos se vuelven más complejos, gestionarlos o arrays se vuelve mucho más difícil y complejo con el useState
Hook. En este caso, podemos usar el useReducer
Hook.
Modifiquemos nuestro useTodo
Hook para que funcione con el useReducer
Hook:
importar {useReducer} de 'react'; interfaz de exportación TodoItemProps { título : cadena ; está completo: booleano; } enumeración TodoActionType { ADD_ITEM = 'agregar-item' , REMOVE_ITEM = 'eliminar-item' , MARK_ITEM_COMPLETE = 'marcar-completo' , } const reducerFn = ( estado : { todos : TodoItemProps [] }, acción : { tipo : TodoActionType ; carga útil : cadena | número } ): { todos : TodoItemProps [] } = { const { carga útil , tipo } = acción ; switch ( tipo ) { caso TodoActionType . AGREGAR_ÍTEM : { devolver { ... estado , todos : [ ... estado . todos , { título : carga útil . toString (), isComplete : falso }, ], }; } caso TodoActionType . REMOVE_ÍTEM : { devolver { ... estado , todos : [... estado . todos. filtro (( _ , ind ) = ind !== carga útil )], }; } caso TodoActionType . MARCAR_ÍTEM_COMPLETE: { devolver {... estado, todos: estado. todos. mapa (( todo , ind ) = ind === carga útil ? { ... todo , completado : verdadero } : todo ), }; } predeterminado : { estado de retorno ; } } }; exportar const useTodo = () = { const [ estado , despacho ] = useReducer ( reducerFn , { todos : [], }); const addTodoItem = (título: cadena) = { despacho({ tipo: TodoActionType.ADD_ITEM, carga útil: título }); }; const removeTodoItem = (id: numero) = { dispatch({ tipo: TodoActionType.REMOVE_ITEM, carga útil: id }); }; const markComplete = (id: numero) = { dispatch({ tipo: TodoActionType.MARK_ITEM_COMPLETE, carga útil: id }); }; return { todos: estado.todos, addTodoItem, removeTodoItem, markComplete };};
Aunque no es la opción ideal en nuestro ejemplo, useReducer
puede ser útil para actualizaciones extensas en varios estados. Aquí, podemos actualizar fácilmente nuestros estados mediante un tipo de acción predefinido mediante el dispatch
método, y la lógica de negocio principal se gestiona dentro de la función reductora.
Descripción general de Immer
Immer es un paquete ligero que simplifica el trabajo con estados inmutables. Las estructuras de datos inmutables garantizan una detección eficiente de cambios en los datos, lo que facilita el seguimiento de las modificaciones. Además, permiten una clonación rentable al compartir las partes inalteradas de un árbol de datos en memoria.
Analicemos nuestro ejemplo de tarea pendiente y veamos cómo podemos usar Immer en nuestro ejemplo:
importar { useImmer } de 'use-immer'; exportar interfaz TodoItemProps { título: cadena; isCompleted: boolean;} exportar const useTodo = () = { const [todos, updateTodos] = useImmerTodoItemProps[]([]); const addTodoItem = (título: cadena) = { updateTodos((borrador) = { borrador.push({ título, isCompleted: false }); }); }; const removeTodoItem = (id: numero) = { updateTodos((borrador) = { borrador.splice(id, 1); }); }; const markComplete = (id: numero) = { updateAll((borrador) = { borrador[id].isCompleted = true; }); }; devolver { todo, agregarTodoItem, eliminarTodoItem, marcarCompletado };};
En este caso, podemos modificar nuestras tareas inmutables con Immer. Immer primero obtiene el estado base, crea un borrador, permite modificarlo y luego devuelve el estado modificado, lo que permite que el estado original sea inmutable.
6 herramientas CSS para un manejo más eficiente y flexible de CSSEn este ejemplo, usamos use-immer
Hook, que se puede agregar ejecutando npm run immer use-immer
.
Descripción general de Mutative
La implementación de Mutative es similar a la de Immer, pero más robusta. Mutative procesa los datos con mejor rendimiento que Immer y los reductores nativos.
Según el equipo de Mutative, esta herramienta de gestión de estados ayuda a que las actualizaciones inmutables sean más eficientes. Se dice que es entre dos y seis veces más rápida que un reductor simple y más de diez veces más rápida que Immer. Esto se debe a que:
- Tiene características adicionales como copia superficial personalizada (soporte para más tipos de datos inmutables)
- No permite la congelación de datos inmutables de forma predeterminada
- Permite el marcado no invasivo de datos inmutables y mutables.
- Admite un acceso a datos mutables más seguro en modo estricto
- También admite funciones reductoras y cualquier otra biblioteca de estados inmutables.
Veamos nuestro ejemplo con Mutative a continuación:
importar { useMutative } de 'use-mutative'; exportar interfaz TodoItemProps { título: cadena; estáCompletado: booleano;} exportar const useTodo = () = { const [todos, setTodos] = useMutativeTodoItemProps[]([]); const addTodoItem = (título: cadena) = { setTodos((borrador) = { borrador.push({ título, estáCompletado: falso }); }); }; const removeTodoItem = (id: número) = { setTodos((borrador) = { borrador.splice(id, 1); }); }; const markComplete = (id: número) = { setTodos((borrador) = { borrador[id].isComplete = verdadero; }); }; devolver { todos, addTodoItem, removeTodoItem, markComplete };};
En este ejemplo utilizamos el use-mutative
Hook, que se puede agregar ejecutando el npm run mutative use-mutative
comando.
Comparación de mutativos, inmersivos y reductores
Con base en las discusiones anteriores, aquí hay una tabla de comparación entre los Mutantes, Inmersos y Reductores:
Reductor | Siempre | Mutativo | |
---|---|---|---|
Concepto | Una función pura que toma el estado actual y un objeto de acción como argumentos y devuelve un nuevo objeto de estado. | Una biblioteca que proporciona una forma más sencilla de crear actualizaciones inmutables al permitirle escribir mutaciones como si los datos fueran mutables | Una biblioteca de JavaScript para actualizaciones inmutables eficientes |
Usar | Los reductores se utilizan normalmente con Redux, una biblioteca de gestión de estados para aplicaciones JavaScript. | Immer simplifica la escritura de actualizaciones inmutables al permitir escribir mutaciones como si los datos fueran mutables. Internamente, Immer crea una copia de los datos y luego aplica las mutaciones a la copia. | Mutative proporciona una API para crear actualizaciones inmutables. Es similar a Immer, ya que permite escribir mutaciones como si los datos fueran mutables, pero afirma tener un mayor rendimiento. |
Inmutabilidad | Los reductores son inherentemente inmutables, ya que deben devolver un nuevo objeto de estado. | Immer crea actualizaciones inmutables copiando los datos y luego aplicando las mutaciones a la copia. | Mutative también crea actualizaciones inmutables |
Actuación | El rendimiento de los reductores puede variar según la complejidad de la función del reductor y el tamaño del objeto de estado. | Immer puede tener cierta sobrecarga de rendimiento debido a la necesidad de copiar los datos antes de aplicar mutaciones. | Mutative afirma tener mayor rendimiento que Reducers e Immer |
Curva de aprendizaje | Los reductores son un concepto relativamente simple de entender. | Immer puede ser más fácil de aprender que los reductores para los desarrolladores que no están familiarizados con la programación funcional. | La API de Mutative es similar a la de Immer, por lo que la curva de aprendizaje debería ser similar. |
Apoyo comunitario | Los reductores son un concepto fundamental en Redux, una popular biblioteca de gestión de estados. Por ello, existe una gran comunidad de desarrolladores familiarizados con ellos. | Immer es una biblioteca relativamente nueva, pero ha ganado cierta popularidad en los últimos años. | Mutative es una biblioteca más nueva que Immer, por lo que el apoyo de la comunidad sigue creciendo. |
Aunque la funcionalidad principal es la misma para todas estas soluciones de gestión estatal, existen diferencias significativas en sus mediciones de rendimiento.
Mutative cuenta con una implementación de pruebas comparativas para comparar el rendimiento de Reducer, Immer y Mutative. Para ejecutar la prueba comparativa, primero, clone el repositorio de Mutative desde GitHub con el siguiente comando:
clon git git@github.com:unadlib/mutative.git
Luego instale las dependencias y ejecute el benchmark usando el siguiente comando:
Instalación de hilo, punto de referencia de hilo
Obtendrá los siguientes diagramas al ejecutar el benchmark. Puede obtenerlos en el directorio base del repositorio de mutative. Primero, verá un informe de rendimiento que muestra que Mutative supera a Immer:
Luego, debería ver una comparación del rendimiento de Mutative frente a los reductores para una matriz, que muestra que cuanto más elementos haya en la matriz, más tiempo requieren los reductores, mientras que el requisito de tiempo de Mutative solo aumenta ligeramente:
El siguiente diagrama compara el rendimiento de Mutative y los reductores para un objeto. Aquí se puede observar que cuantos más elementos tenga un objeto, más tiempo requieren tanto los reductores como Mutative. Sin embargo, el tiempo necesario es mucho menor para Mutative que para los reductores a medida que aumenta el número de elementos en el objeto:
Finalmente, verá un diagrama que compara el rendimiento de Mutative e Immer para una clase. Al igual que en las comparaciones anteriores, observamos que, a medida que aumenta el número de claves de una clase, el tiempo de ejecución de Immer aumenta drásticamente en comparación con Mutative, lo que demuestra que Mutative es una excelente opción para optimizar el rendimiento.
De las métricas anteriores, podemos observar que Mutative ofrece un rendimiento más potente, especialmente al gestionar grandes volúmenes de datos. Por otro lado, los reductores e Immer no pueden gestionar grandes cantidades de datos, a diferencia de las mutaciones, especialmente matrices y clases.
En general, en nuestra prueba de referencia, Mutative ocupa el primer lugar, los reductores el segundo y Immer obtiene la última posición.
Conclusión
La mayoría de los desarrolladores usan reductores, ya que vienen integrados en React. Sin embargo, Mutative es una mejor opción para gestionar grandes cantidades de datos en una aplicación React.
Puedes consultar el ejemplo anterior en este repositorio de SlackBlitz. Si tienes más preguntas, puedes comentar a continuación. Si exploras Mutative, no dudes en compartir tus ideas.
Empieza ahora
Si quieres conocer otros artículos parecidos a Comparación de herramientas de estado de React: Mutative, Immer y reductores puedes visitar la categoría Herramientas.
Entradas Relacionadas