跳到主要内容前端状态管理指南:Redux Toolkit、Zustand 与 Jotai 对比 | 极客日志JavaScript大前端
前端状态管理指南:Redux Toolkit、Zustand 与 Jotai 对比
对比了三种主流前端状态管理方案:Redux Toolkit、Zustand 和 Jotai。通过反面教材展示传统组件传参的弊端,并分别提供基于这三种库的正确实现代码。Redux Toolkit 适合大型复杂应用,Zustand 轻量灵活,Jotai 基于原子化状态。帮助开发者根据项目需求选择合适的状态管理工具,避免状态混乱。
清酒独酌6 浏览 前端状态管理指南:Redux Toolkit、Zustand 与 Jotai 对比
问题剖析
这状态管理得跟蜘蛛网似的,谁能理得清?
各位前端同行,咱们今天聊聊前端状态管理。别告诉我你还在使用 setState 管理所有状态,那感觉就像在没有地图的情况下寻宝——能找,但累死你。
为什么你需要状态管理
最近看到一个项目,组件之间传递状态需要经过 5 层,修改一个状态要修改多个地方。我就想问:你是在做状态管理还是在做传递游戏?
反面教材
function App() {
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
const [comments, setComments] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchData() {
setLoading(true);
try {
const userResponse = await fetch('/api/user');
const userData = await userResponse.json();
setUser(userData);
const postsResponse = await fetch('/api/posts');
const postsData = await postsResponse.json();
setPosts(postsData);
const commentsResponse = ();
commentsData = commentsResponse.();
(commentsData);
} (error) {
.(, error);
} {
();
}
}
();
}, []);
(
);
}
() {
;
}
() {
(
);
}
() {
postComments = comments.( comment. === postId);
(
);
}
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
await
fetch
'/api/comments'
const
await
json
setComments
catch
console
error
'Error fetching data:'
finally
setLoading
false
fetchData
return
<div>
{loading ? <div>加载中...</div> : (
<div>
<UserHeader user={user} />
<PostList posts={posts} comments={comments} />
</div>
)}
</div>
function
UserHeader
{ user }
return
<h1>Welcome, {user?.name}</h1>
function
PostList
{ posts, comments }
return
<div>
{posts.map(post => (
<div key={post.id}>
<h2>{post.title}</h2>
<p>{post.content}</p>
<CommentList postId={post.id} comments={comments} />
</div>
))}
</div>
function
CommentList
{ postId, comments }
const
filter
comment =>
postId
return
<div>
{postComments.map(comment => (
<div key={comment.id}>{comment.content}</div>
))}
</div>
方案总结:这状态管理,就像在玩传球游戏,球传来传去都不知道传到哪里了。
正确姿势
1. Redux Toolkit
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './slices/userSlice';
import postsReducer from './slices/postsSlice';
import commentsReducer from './slices/commentsSlice';
export const store = configureStore({
reducer: {
user: userReducer,
posts: postsReducer,
comments: commentsReducer,
},
});
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
export const fetchUser = createAsyncThunk('user/fetchUser', async () => {
const response = await fetch('/api/user');
return response.json();
});
const userSlice = createSlice({
name: 'user',
initialState: {
data: null,
loading: false,
error: null,
},
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUser.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchUser.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
})
.addCase(fetchUser.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
});
},
});
export default userSlice.reducer;
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux';
import { store } from './store';
ReactDOM.createRoot(document.getElementById('root')).render(
<Provider store={store}>
<App />
</Provider>
);
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchUser } from './store/slices/userSlice';
import { fetchPosts } from './store/slices/postsSlice';
import { fetchComments } from './store/slices/commentsSlice';
import UserHeader from './components/UserHeader';
import PostList from './components/PostList';
function App() {
const dispatch = useDispatch();
const { loading } = useSelector((state) => state.user);
useEffect(() => {
dispatch(fetchUser());
dispatch(fetchPosts());
dispatch(fetchComments());
}, [dispatch]);
return (
<div>
{loading ? <div>加载中...</div> : (
<div>
<UserHeader />
<PostList />
</div>
)}
</div>
);
}
import React from 'react';
import { useSelector } from 'react-redux';
function UserHeader() {
const user = useSelector((state) => state.user.data);
return <h1>Welcome, {user?.name}</h1>;
}
export default UserHeader;
import React from 'react';
import { useSelector } from 'react-redux';
import CommentList from './CommentList';
function PostList() {
const posts = useSelector((state) => state.posts.data);
return (
<div>
{posts.map(post => (
<div key={post.id}>
<h2>{post.title}</h2>
<p>{post.content}</p>
<CommentList postId={post.id} />
</div>
))}
</div>
);
}
export default PostList;
import React from 'react';
import { useSelector } from 'react-redux';
function CommentList({ postId }) {
const comments = useSelector((state) => state.comments.data.filter(comment => comment.postId === postId));
return (
<div>
{comments.map(comment => (
<div key={comment.id}>{comment.content}</div>
))}
</div>
);
}
export default CommentList;
2. Zustand
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
const useStore = create(
persist(
(set, get) => ({
user: null,
posts: [],
comments: [],
loading: false,
error: null,
fetchUser: async () => {
set({ loading: true, error: null });
try {
const response = await fetch('/api/user');
const data = await response.json();
set({ user: data, loading: false });
} catch (error) {
set({ error: error.message, loading: false });
}
},
fetchPosts: async () => {
set({ loading: true, error: null });
try {
const response = await fetch('/api/posts');
const data = await response.json();
set({ posts: data, loading: false });
} catch (error) {
set({ error: error.message, loading: false });
}
},
fetchComments: async () => {
set({ loading: true, error: null });
try {
const response = await fetch('/api/comments');
const data = await response.json();
set({ comments: data, loading: false });
} catch (error) {
set({ error: error.message, loading: false });
}
},
}),
{ name: 'app-storage' }
)
);
export default useStore;
import React, { useEffect } from 'react';
import useStore from './store';
import UserHeader from './components/UserHeader';
import PostList from './components/PostList';
function App() {
const { loading, fetchUser, fetchPosts, fetchComments } = useStore();
useEffect(() => {
fetchUser();
fetchPosts();
fetchComments();
}, [fetchUser, fetchPosts, fetchComments]);
return (
<div>
{loading ? <div>加载中...</div> : (
<div>
<UserHeader />
<PostList />
</div>
)}
</div>
);
}
import React from 'react';
import useStore from '../store';
function UserHeader() {
const user = useStore((state) => state.user);
return <h1>Welcome, {user?.name}</h1>;
}
export default UserHeader;
import React from 'react';
import useStore from '../store';
import CommentList from './CommentList';
function PostList() {
const posts = useStore((state) => state.posts);
return (
<div>
{posts.map(post => (
<div key={post.id}>
<h2>{post.title}</h2>
<p>{post.content}</p>
<CommentList postId={post.id} />
</div>
))}
</div>
);
}
export default PostList;
import React from 'react';
import useStore from '../store';
function CommentList({ postId }) {
const comments = useStore((state) => state.comments.filter(comment => comment.postId === postId));
return (
<div>
{comments.map(comment => (
<div key={comment.id}>{comment.content}</div>
))}
</div>
);
}
export default CommentList;
3. Jotai
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
const userAtom = atomWithStorage('user', null);
const postsAtom = atomWithStorage('posts', []);
const commentsAtom = atomWithStorage('comments', []);
const loadingAtom = atom(false);
const errorAtom = atom(null);
const fetchUserAtom = atom(
null,
async (_, set) => {
set(loadingAtom, true);
set(errorAtom, null);
try {
const response = await fetch('/api/user');
const data = await response.json();
set(userAtom, data);
} catch (error) {
set(errorAtom, error.message);
} finally {
set(loadingAtom, false);
}
}
);
const fetchPostsAtom = atom(
null,
async (_, set) => {
set(loadingAtom, true);
set(errorAtom, null);
try {
const response = await fetch('/api/posts');
const data = await response.json();
set(postsAtom, data);
} catch (error) {
set(errorAtom, error.message);
} finally {
set(loadingAtom, false);
}
}
);
const fetchCommentsAtom = atom(
null,
async (_, set) => {
set(loadingAtom, true);
set(errorAtom, null);
try {
const response = await fetch('/api/comments');
const data = await response.json();
set(commentsAtom, data);
} catch (error) {
set(errorAtom, error.message);
} finally {
set(loadingAtom, false);
}
}
);
export { userAtom, postsAtom, commentsAtom, loadingAtom, errorAtom, fetchUserAtom, fetchPostsAtom, fetchCommentsAtom };
import React, { useEffect } from 'react';
import { useAtomValue, useSetAtom } from 'jotai';
import { loadingAtom, fetchUserAtom, fetchPostsAtom, fetchCommentsAtom } from './store/atoms';
import UserHeader from './components/UserHeader';
import PostList from './components/PostList';
function App() {
const loading = useAtomValue(loadingAtom);
const fetchUser = useSetAtom(fetchUserAtom);
const fetchPosts = useSetAtom(fetchPostsAtom);
const fetchComments = useSetAtom(fetchCommentsAtom);
useEffect(() => {
fetchUser();
fetchPosts();
fetchComments();
}, [fetchUser, fetchPosts, fetchComments]);
return (
<div>
{loading ? <div>加载中...</div> : (
<div>
<UserHeader />
<PostList />
</div>
)}
</div>
);
}
import React from 'react';
import { useAtomValue } from 'jotai';
import { userAtom } from '../store/atoms';
function UserHeader() {
const user = useAtomValue(userAtom);
return <h1>Welcome, {user?.name}</h1>;
}
export default UserHeader;
import React from 'react';
import { useAtomValue } from 'jotai';
import { postsAtom } from '../store/atoms';
import CommentList from './CommentList';
function PostList() {
const posts = useAtomValue(postsAtom);
return (
<div>
{posts.map(post => (
<div key={post.id}>
<h2>{post.title}</h2>
<p>{post.content}</p>
<CommentList postId={post.id} />
</div>
))}
</div>
);
}
export default PostList;
import React from 'react';
import { useAtomValue } from 'jotai';
import { commentsAtom } from '../store/atoms';
function CommentList({ postId }) {
const comments = useAtomValue(commentsAtom);
const postComments = comments.filter(comment => comment.postId === postId);
return (
<div>
{postComments.map(comment => (
<div key={comment.id}>{comment.content}</div>
))}
</div>
);
}
export default CommentList;
方案总结:这才叫前端状态管理,集中管理状态,组件之间共享状态,再也不用担心状态传递的问题了。