前端状态管理:别让你的状态变成一团乱麻

前端状态管理:别让你的状态变成一团乱麻

毒舌时刻

这状态管理得跟蜘蛛网似的,谁能理得清?

各位前端同行,咱们今天聊聊前端状态管理。别告诉我你还在使用 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 = await fetch('/api/comments'); const commentsData = await commentsResponse.json(); setComments(commentsData); } catch (error) { console.error('Error fetching data:', error); } 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 postComments = comments.filter(comment => comment.postId === postId); return ( <div> {postComments.map(comment => ( <div key={comment.id}>{comment.content}</div> ))} </div> ); } 

毒舌点评:这状态管理,就像在玩传球游戏,球传来传去都不知道传到哪里了。

正确姿势

1. Redux Toolkit

// 正确姿势:Redux Toolkit // 1. 安装依赖 // npm install @reduxjs/toolkit react-redux // 2. 创建 store // store/index.js 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, }, }); // 3. 创建 slices // store/slices/userSlice.js 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; // 4. 使用 store // index.js 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> ); // 5. 使用状态 // App.jsx 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> ); } // components/UserHeader.jsx 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; // components/PostList.jsx 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; // components/CommentList.jsx 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

// 正确姿势:Zustand // 1. 安装依赖 // npm install zustand // 2. 创建 store // store/index.js 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; // 3. 使用 store // App.jsx 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> ); } // components/UserHeader.jsx 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; // components/PostList.jsx 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; // components/CommentList.jsx 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

// 正确姿势:Jotai // 1. 安装依赖 // npm install jotai // 2. 创建 atoms // store/atoms.js import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'; import { atomWithStorage } from 'jotai/utils'; // 状态 atoms const userAtom = atomWithStorage('user', null); const postsAtom = atomWithStorage('posts', []); const commentsAtom = atomWithStorage('comments', []); const loadingAtom = atom(false); const errorAtom = atom(null); // 动作 atoms 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 }; // 3. 使用 atoms // App.jsx 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> ); } // components/UserHeader.jsx 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; // components/PostList.jsx 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; // components/CommentList.jsx 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; 

毒舌点评:这才叫前端状态管理,集中管理状态,组件之间共享状态,再也不用担心状态传递的问题了。

Read more

从“手机上写代码“的疯狂想法,到一套完整的AI编程平台架构——WebCode深度技术剖析

从“手机上写代码“的疯狂想法,到一套完整的AI编程平台架构——WebCode深度技术剖析

说实话,当我第一次在地铁上用手机修复了一个线上Bug的时候,我整个人都是懵的。不是因为Bug有多难,而是因为——我TM居然真的在手机上写代码了? 一、那个让我失眠的需求 故事要从去年说起。 当时我们团队接到一个"看起来很简单"的需求:让开发者能够随时随地使用AI编程助手。产品经理信誓旦旦地说:"不就是做个Web版的Claude Code吗?套个壳就行了。" 我当时就笑了。 你知道Claude Code CLI是怎么工作的吗?它是一个本地进程,需要读写文件系统,需要执行Shell命令,需要维护会话状态,需要解析流式JSON输出...把这玩意儿搬到Web上?还要支持手机? 那几天我躺在床上翻来覆去睡不着,脑子里全是问题: * 浏览器怎么调用本地CLI工具? * 流式输出怎么实时推送到前端? * 多个用户同时使用,工作区怎么隔离? * 手机上那个破键盘,怎么让人愉快地写代码? * Claude Code和Codex的输出格式完全不一样,怎么统一处理? 后来我想明白了一件事:这不是一个"套壳"的活儿,这是要从零设计一套分布式AI编程平台的架构。

AI Skills:前端新的效率神器!

近来,AI 领域有个火爆的话题:Skills。 Github 上被疯狂 star 的仓库,很多都是和 skills 有关的。 有的仓库仅仅上线三个月就获得了快 50K 的 star,Skills 的火热可见一斑。 不管是大模型,还是 Cursor、Codex、Claude、Trae、Copilot 等编程 IDE 都在争先支持 Skills。 围绕 Skills,它们在做的就是为了完成一件事情:技能是通过学习和反复练习获得的,而 Skills 是把经验和最佳实践沉淀为 AI 能力,将“知道”转化为“做到”的本领。 详解什么是 Skills 要说清楚什么是 Skills,先来了解一下关于 AI 的 2

【保姆级教程】告别命令行!ClawX:首款 OpenClaw 可视化桌面客户端,零门槛玩转 AI 智能体!

目录 1、为什么选择 ClawX?(核心亮点) 🎯 零配置门槛 (Zero Configuration) 💬 现代化的聊天体验 ⏰ 可视化的自动化任务 (Cron Automation) 🧩 技能插件市场 (Skill System) 2、技术揭秘:它是如何工作的? 3、快速上手指南 4、注册并获取高性能 API 5、在 ClawX 中接入 API 6、验证连接与初次体验 🚀 结语:这只是冰山一角 在这个“万物皆可 Agent”的时代,我们见证了 OpenClaw 这样优秀的开源项目如何重新定义了 AI 任务编排。它强大、灵活,能帮我们串联起各种复杂的 AI 工作流。 但是,你是否也曾有过这样的困扰? * 想要体验最新的 AI

什么是流式输出,后端怎么生成,前端怎么渲染

什么是流式输出,后端怎么生成,前端怎么渲染 流式输出(Streaming Output) 就像是在看视频直播,内容是一边产生一边传输给你的,而不是像下载电影那样,必须等整个文件下完才能开始看。 在 AI 领域(比如 ChatGPT),流式输出表现为文字一个接一个地“蹦”出来,而不是转半天圈圈后突然甩出一大段话。 什么是流式输出,有什么特点 1. 它是怎么实现的? 流式输出的核心技术通常是 SSE (Server-Sent Events,服务器发送事件)。 在传统的 HTTP 请求中,模式是“一问一答”:客户端发请求,服务器处理完全部逻辑,打成一个大包发回客户端。而在流式输出中,过程如下: 1. 建立持久连接:客户端发送一个请求,并在 HTTP 头部声明 Accept: text/event-stream。 2. 分块传输:服务器每生成一个字(