Zustand vs Jotai: Eligiendo el Gestor de Estado Correcto para tu Aplicación React

¿Nuevo en Zustand o Jotai? Consulta primero nuestras guías detalladas:
La gestión de estado en React ha evolucionado significativamente más allá de la complejidad de Redux. Para proyectos pequeños a medianos, alternativas ligeras como Zustand y Jotai han ganado popularidad. ¿Pero cuál deberías elegir? Este artículo compara estas dos bibliotecas creadas por el mismo desarrollador (Daishi Kato) para ayudarte a tomar una decisión informada basada en las necesidades de tu proyecto.
Puntos Clave
- Zustand utiliza un enfoque centralizado y descendente, ideal para estado interconectado y colaboración en equipo
- Jotai utiliza un enfoque atómico y ascendente, perfecto para reactividad granular y datos que cambian rápidamente
- Ambas bibliotecas son ligeras, eficientes y compatibles con TypeScript
- Zustand es a menudo mejor para aplicaciones más grandes con relaciones de estado complejas
- Jotai destaca en escenarios que requieren piezas independientes de estado con re-renderizados mínimos
Los Orígenes y Filosofía de Zustand y Jotai
Ambas bibliotecas fueron creadas para resolver problemas específicos en el ecosistema de React, pero con enfoques diferentes:
Zustand: El Enfoque Descendente
Zustand (alemán para “estado”) fue lanzado en 2019 como una alternativa más simple a Redux. Sigue un enfoque centralizado y descendente para la gestión de estado.
import { create } from 'zustand'
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))
Jotai: El Enfoque Ascendente
Jotai (japonés para “estado”) fue lanzado en 2020 y se inspira en Recoil. Utiliza un enfoque atómico y ascendente donde el estado se divide en átomos pequeños e independientes.
import { atom, useAtom } from 'jotai'
const countAtom = atom(0)
const doubleCountAtom = atom((get) => get(countAtom) * 2)
Modelos Mentales: Cómo Abordan el Estado de Manera Diferente
Entender el modelo mental detrás de cada biblioteca es crucial para elegir la correcta para tu proyecto.
Modelo Basado en Store de Zustand
Zustand utiliza un store único que contiene todo tu estado y acciones. Este modelo es familiar para desarrolladores que han usado Redux:
// Creando un store
const useUserStore = create((set) => ({
user: null,
isLoading: false,
error: null,
fetchUser: async (id) => {
set({ isLoading: true });
try {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
set({ user, isLoading: false });
} catch (error) {
set({ error, isLoading: false });
}
}
}));
// Usando el store en un componente
function Profile({ userId }) {
const { user, fetchUser } = useUserStore(
state => ({ user: state.user, fetchUser: state.fetchUser })
);
useEffect(() => {
fetchUser(userId);
}, [userId, fetchUser]);
return user ? <div>{user.name}</div> : <div>Loading...</div>;
}
Modelo Atómico de Jotai
Jotai divide el estado en átomos que pueden ser compuestos juntos. Esto es similar al useState
propio de React pero con la capacidad de compartir estado entre componentes:
// Creando átomos
const userAtom = atom(null);
const isLoadingAtom = atom(false);
const errorAtom = atom(null);
// Creando un átomo derivado
const userStatusAtom = atom(
(get) => ({
user: get(userAtom),
isLoading: get(isLoadingAtom),
error: get(errorAtom)
})
);
// Creando un átomo de acción
const fetchUserAtom = atom(
null,
async (get, set, userId) => {
set(isLoadingAtom, true);
try {
const response = await fetch(`/api/users/${userId}`);
const user = await response.json();
set(userAtom, user);
set(isLoadingAtom, false);
} catch (error) {
set(errorAtom, error);
set(isLoadingAtom, false);
}
}
);
// Usando átomos en un componente
function Profile({ userId }) {
const [{ user, isLoading }] = useAtom(userStatusAtom);
const [, fetchUser] = useAtom(fetchUserAtom);
useEffect(() => {
fetchUser(userId);
}, [userId, fetchUser]);
return user ? <div>{user.name}</div> : <div>Loading...</div>;
}
Consideraciones de Rendimiento: Zustand vs Jotai
Ambas bibliotecas están diseñadas para ser eficientes, pero optimizan para diferentes escenarios.
Perfil de Rendimiento de Zustand
- Suscripciones Selectivas: Los componentes solo se re-renderizan cuando cambia su estado seleccionado
- Tamaño del Bundle: ~2.8kB minificado y comprimido con gzip
- Soporte de Middleware: Middleware integrado para optimización de rendimiento
- Actualizaciones por Lotes: Agrupa automáticamente las actualizaciones de estado
Perfil de Rendimiento de Jotai
- Actualizaciones Granulares: Solo los componentes que usan átomos específicos se re-renderizan
- Tamaño del Bundle: ~3.5kB minificado y comprimido con gzip (paquete core)
- Optimización a Nivel de Átomo: Control granular sobre qué cambios de estado desencadenan re-renderizados
- Estado Derivado: Maneja eficientemente valores computados
Para datos que cambian rápidamente y afectan solo partes específicas de tu UI, el enfoque atómico de Jotai a menudo resulta en menos re-renderizados. Para estado interconectado que cambia con menos frecuencia, el enfoque de Zustand puede ser más eficiente.
Integración con TypeScript
Ambas bibliotecas proporcionan excelente soporte para TypeScript, pero con enfoques diferentes.
Zustand con TypeScript
interface BearState {
bears: number;
increase: (by: number) => void;
}
const useBearStore = create<BearState>((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}));
Jotai con TypeScript
interface User {
id: string;
name: string;
}
const userAtom = atom<User | null>(null);
const nameAtom = atom(
(get) => get(userAtom)?.name || '',
(get, set, newName: string) => {
const user = get(userAtom);
if (user) {
set(userAtom, { ...user, name: newName });
}
}
);
Cuándo Elegir Zustand
Zustand es a menudo la mejor opción cuando:
-
Necesitas un store centralizado: Para aplicaciones con estado interconectado que necesita ser accedido y modificado desde muchos componentes.
-
Estás migrando desde Redux: La API de Zustand es más familiar para usuarios de Redux, facilitando la migración.
-
Necesitas acceso al estado fuera de React: Zustand permite acceder y modificar el estado fuera de componentes React.
-
La colaboración en equipo es una prioridad: El enfoque de store centralizado puede ser más fácil de mantener en equipos grandes.
-
Prefieres actualizaciones de estado explícitas: El enfoque de Zustand hace que los cambios de estado sean más rastreables.
Cuándo Elegir Jotai
Jotai brilla cuando:
-
Necesitas reactividad granular: Para UIs con muchas piezas independientes de estado que cambian frecuentemente.
-
Estás construyendo formularios complejos: El enfoque atómico de Jotai funciona bien para campos de formulario que necesitan ser validados independientemente.
-
Quieres una API similar a useState: Si prefieres una API que se parezca mucho a los hooks integrados de React.
-
Trabajas con datos que cambian rápidamente: Para aplicaciones en tiempo real donde minimizar re-renderizados es crítico.
-
Necesitas estado derivado: Jotai facilita la creación de valores computados basados en otro estado.
Patrones de Implementación del Mundo Real
Veamos algunos patrones comunes implementados en ambas bibliotecas.
Estado de Autenticación
Con Zustand:
const useAuthStore = create((set) => ({
user: null,
isAuthenticated: false,
isLoading: false,
login: async (credentials) => {
set({ isLoading: true });
try {
const user = await loginApi(credentials);
set({ user, isAuthenticated: true, isLoading: false });
} catch (error) {
set({ isLoading: false });
throw error;
}
},
logout: async () => {
await logoutApi();
set({ user: null, isAuthenticated: false });
}
}));
Con Jotai:
const userAtom = atom(null);
const isAuthenticatedAtom = atom((get) => !!get(userAtom));
const isLoadingAtom = atom(false);
const loginAtom = atom(
null,
async (get, set, credentials) => {
set(isLoadingAtom, true);
try {
const user = await loginApi(credentials);
set(userAtom, user);
set(isLoadingAtom, false);
} catch (error) {
set(isLoadingAtom, false);
throw error;
}
}
);
const logoutAtom = atom(
null,
async (get, set) => {
await logoutApi();
set(userAtom, null);
}
);
Gestión de Estado de Formularios
Con Zustand:
const useFormStore = create((set) => ({
values: { name: '', email: '', message: '' },
errors: {},
setField: (field, value) => set(state => ({
values: { ...state.values, [field]: value }
})),
validate: () => {
// Lógica de validación
const errors = {};
set({ errors });
return Object.keys(errors).length === 0;
},
submit: () => {
// Lógica de envío
}
}));
Con Jotai:
const formAtom = atom({ name: '', email: '', message: '' });
const nameAtom = atom(
(get) => get(formAtom).name,
(get, set, name) => set(formAtom, { ...get(formAtom), name })
);
const emailAtom = atom(
(get) => get(formAtom).email,
(get, set, email) => set(formAtom, { ...get(formAtom), email })
);
const messageAtom = atom(
(get) => get(formAtom).message,
(get, set, message) => set(formAtom, { ...get(formAtom), message })
);
const errorsAtom = atom({});
const validateAtom = atom(
null,
(get, set) => {
// Lógica de validación
const errors = {};
set(errorsAtom, errors);
return Object.keys(errors).length === 0;
}
);
Estrategias de Migración
De Redux a Zustand
// Redux
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: state => {
state.value += 1;
},
decrement: state => {
state.value -= 1;
}
}
});
// Zustand
const useCounterStore = create((set) => ({
value: 0,
increment: () => set(state => ({ value: state.value + 1 })),
decrement: () => set(state => ({ value: state.value - 1 }))
}));
De Context API a Jotai
// Context API
const CounterContext = createContext();
function CounterProvider({ children }) {
const [count, setCount] = useState(0);
return (
<CounterContext.Provider value={{ count, setCount }}>
{children}
</CounterContext.Provider>
);
}
// Jotai
const countAtom = atom(0);
function App() {
return <>{/* No se necesita provider */}</>;
}
Errores Comunes y Mejores Prácticas
Errores de Zustand
- Fragmentación de Store: Crear demasiados stores puede llevar a confusión en la gestión de estado.
- Memoización de Selectores: Olvidar memoizar selectores puede causar re-renderizados innecesarios.
- Uso Excesivo de Middleware: Agregar demasiado middleware puede impactar el rendimiento.
Errores de Jotai
- Proliferación de Átomos: Crear demasiados átomos sin organización puede hacer el código difícil de seguir.
- Dependencias Circulares: Crear átomos que dependen entre sí de manera circular.
- Duplicación de Átomos: Crear accidentalmente múltiples instancias del mismo átomo.
Mejores Prácticas para Ambos
- Organizar Estado Relacionado: Agrupar estado y acciones relacionadas juntas.
- Usar TypeScript: Ambas bibliotecas se benefician de la seguridad de tipos de TypeScript.
- Documentar tu Estructura de Estado: Hacer claro cómo está organizado tu estado.
- Probar tu Lógica de Estado: Escribir pruebas unitarias para tu código de gestión de estado.
Conclusión
Elegir entre Zustand y Jotai depende de los requisitos específicos de tu proyecto. Zustand ofrece un enfoque centralizado que funciona bien para estado complejo e interconectado en aplicaciones más grandes. Jotai proporciona un modelo atómico que destaca en reactividad granular con re-renderizados mínimos. Ambas bibliotecas ofrecen soluciones ligeras y eficientes que mejoran significativamente la complejidad de Redux mientras mantienen compatibilidad con TypeScript.
Considera la familiaridad de tu equipo con diferentes patrones de gestión de estado, las necesidades de rendimiento de tu aplicación y tu estructura de estado al tomar tu decisión. Recuerda que incluso puedes usar ambas bibliotecas en la misma aplicación, aprovechando cada una para lo que hace mejor.
Preguntas Frecuentes
Sí, muchos desarrolladores usan Zustand para estado global de la aplicación y Jotai para estado específico de componentes que necesita ser compartido a través del árbol de componentes.
Sí, ambas pueden escalar a aplicaciones grandes cuando se usan correctamente. Zustand podría ser más fácil de mantener en entornos de equipos grandes debido a su enfoque centralizado.
Ambas son significativamente más ligeras y simples que Redux. Zustand está más cerca de Redux en filosofía pero con mucho menos código repetitivo. Jotai toma un enfoque completamente diferente enfocado en estado atómico.
Zustand no requiere un provider por defecto. Jotai puede usarse sin provider para átomos globales pero ofrece un provider para estado con alcance específico.
Sí, ambas bibliotecas funcionan bien con Next.js y otros frameworks de React. Proporcionan utilidades específicas para soporte de renderizado del lado del servidor.