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

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

毒舌时刻

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

各位前端同行,咱们今天聊聊前端状态管理。别告诉我你还在使用 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

简单理解:单片机怎么和FPGA通信

了解单片机与 FPGA 之间的通信方式,这是嵌入式系统中非常常见的硬件交互场景,核心是要根据传输速率、硬件资源、开发复杂度选择合适的通信协议。 一、主流通信方式及实现方案 单片机和 FPGA 通信主要分为并行通信和串行通信两大类,下面按从易到难、从低速到高速的顺序介绍: 1. 通用 IO 口(GPIO)自定义协议(最简单) 适合低速、短距离、数据量小的场景(如按键、状态交互),完全自定义通信规则,开发灵活。 * 硬件连接: * 单片机:1 个输出引脚(发送) + 1 个输入引脚(接收) * FPGA:1 个输入引脚(接收) + 1 个输出引脚(发送) * 需共地,建议加 10K 上拉电阻提高稳定性。 * 单片机端(C 语言,

把 Vivado 项目放心交给 Git:一篇 FPGA 工程师必读的实战指南

之前分享过一篇文章《FPGA 版本管理三种方式:你会选哪一种?》,评论区很多人都推荐使用Git进行版本管理,今天这篇文章主题就是使用Git进行备份指南。 在 FPGA 开发中,掌握 Git 等源码管理工具已经是必备技能。 当然,在使用 Vivado 时,我们不仅需要处理源代码控制,还需要处理以 IP 为中心的设计产品。 Vivado 的工程通常是 IP 为中心 的设计,包含: * IP Integrator Block Diagram * 各类 IP 实例(独立 IP 或 BD 内 IP) * 自动生成的包装文件与工程产物 这让很多 FPGA 工程师一开始会觉得: “Vivado 项目到底该怎么和 Git 一起用?” 好消息是,从 Vivado

OpenClaw安装和接入飞书机器人完整教程

OpenClaw安装和接入飞书机器人分三大部分组织回答: 1)先讲环境准备和OpenClaw基础安装(分阿里云和本地Windows两种场景); 2)再讲飞书机器人配置(包括应用创建、通道添加、事件订阅); 3)最后讲验证和配置AI模型。 为了更直观,在部署方式对比、配置项说明等地方用表格呈现。 这是一份完整的OpenClaw安装及接入飞书机器人的教程。将涵盖从环境准备、OpenClaw部署(含阿里云服务器和本地Windows两种方式)、AI模型(以阿里云百炼为例)配置,到最终在飞书开放平台创建并接入机器人的全流程。 第一部分:准备工作与核心认知 在开始动手前,我们需要先了解 OpenClaw 是什么,并准备好必要的账号和工具。 1.1 什么是 OpenClaw? OpenClaw(昵称“小龙虾”,曾用名 ClawdBot / Moltbot)是一个开源的个人AI智能体框架。它本身不具备推理能力,需要对接大语言模型(如阿里云百炼、七牛云、OpenAI等)的API。它的核心价值在于: * 真正的执行能力:能通过“技能”

Microi吾码:从零到服装ERP:低代码打造企业级系统的实战之旅

Microi吾码:从零到服装ERP:低代码打造企业级系统的实战之旅

个人主页:chian-ocean 文章专栏 从零到服装ERP:吾码平台打造企业级系统的实战之旅 关键词:吾码平台、低代码、服装ERP、多表关系、自动化、开发实例 引言 在传统的服装行业管理中,ERP系统已成为提高效率、降低成本、优化资源分配的核心工具。然而,开发一个功能全面、覆盖采购、库存、销售、财务等模块的ERP系统,往往需要投入大量时间和人力资源。在吾码低代码平台的支持下,1人仅用1个月便完成了包含100+表的企业级服装ERP系统。本文将从项目概述、开发细节到关键代码段详细剖析整个开发过程,展示低代码技术的强大能力。 第一部分:项目概览 1.1 项目背景 * 项目需求: * 支持采购、库存、销售、客户管理、财务报表等多个模块。 * 包括100+数据表,涵盖复杂的业务逻辑与数据关联。 * 需实现流程自动化(如采购审批、库存提醒)。 * 开发目标: * 快速完成开发,并保证系统稳定性与扩展性。