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.

Table
  1. Descripción general de los reductores de React
  2. Descripción general de Immer
  3. Descripción general de Mutative
  4. Comparación de mutativos, inmersivos y reductores
  5. Conclusión

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 React

Los 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 useStatepara 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 todológica en un Hook personalizado para que el código sea más claro. Creemos un nuevo useTodoHook y movemos nuestra lógica aquí:

Automatizar lo cotidiano: 4 herramientas de IA para mejorar la investigación de UX
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 useStateHook 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 useStateHook. En este caso, podemos usar el useReducerHook.

Modifiquemos nuestro useTodoHook para que funcione con el useReducerHook:

Creación de animaciones de UI: tutorial, herramientas y mejores prácticas
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, useReducerpuede ser útil para actualizaciones extensas en varios estados. Aquí, podemos actualizar fácilmente nuestros estados mediante un tipo de acción predefinido mediante el dispatchmé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 CSS

En este ejemplo, usamos use-immerHook, 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-mutativeHook, que se puede agregar ejecutando el npm run mutative use-mutativecomando.

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