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

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

毒舌时刻

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

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

Qt 前后端通信(QWebChannel Js / C++ 互操作):原理、示例、步骤解说

Qt 前后端通信(QWebChannel Js / C++ 互操作):原理、示例、步骤解说

Qt 提供的 QWebEngineView 是一个基于 Chromium 内核的浏览器组件,通过它,开发者可以使用 HTML、CSS、JavaScript 等技术开发 Web 页面并呈现在 Qt 桌面应用中,但与开发纯 Web 页面不同的是,这些页面通常需要和 应用中的其他组件交互,例如获取后端数据进行渲染、将前端用户指令传达给后端执行等,这将不可避免地涉及到前端 Js 和 后端 C++ 之间的交互问题,而 Qt 为此给出的解决方案就是 QWebChannel,通过 QWebChannel 前端 Web 页面和与后端 C++ 程序实现自然而顺畅的交互,甚至前后端的操作风格都极为一致。本文我们将细致地介绍QWebChannel 前后端交互的原理,通过四个详实的示例程序讲解每一步重要的操作步骤,通过本文,你将对 QWebChannel 有一个全面而深入的了解。 1. 工作原理

openclaw喂饭教程!在 Linux 环境下快速完成安装、初始化与 Web UI 配置

openclaw喂饭教程!在 Linux 环境下快速完成安装、初始化与 Web UI 配置

前言 OpenClaw 是一款开源的 AI Agent 工具,但对第一次接触的用户来说,完整跑通流程并不直观。本文以 Linux 环境为例,详细记录了 OpenClaw 的安装、初始化流程、模型选择、TUI 使用方式,以及 TUI 与 Web UI 认证不一致导致的常见问题与解决方法,帮助你最快速度把 OpenClaw 真正跑起来 环境准备 1)安装nodejs curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - sudo apt install -y nodejs > node

实战演练:基于快马平台快速构建一个支持tokenp钱包登录的DApp前端

今天想和大家分享一个实战项目:如何快速构建一个支持TokenP钱包登录的DApp前端。这个项目特别适合想学习Web3开发的初学者,整个过程在InsCode(快马)平台上完成,省去了本地环境配置的麻烦。 1. 项目准备 首先需要明确几个核心功能:钱包连接、用户信息展示、链上数据查询和退出登录。选择Next.js框架是因为它既支持服务端渲染,又能很好地与各种Web3库集成。Wagmi和Viem这两个库是目前最流行的以太坊开发工具组合,能大大简化钱包交互流程。 2. 钱包连接实现 在首页添加"使用钱包登录"按钮后,通过Wagmi提供的useConnect钩子就能轻松实现钱包连接功能。这里需要注意处理用户拒绝连接的情况,以及不同钱包提供商的兼容性问题。TokenP钱包作为移动端主流钱包,通过WalletConnect协议可以很好地与网页应用交互。 3. 用户信息展示 连接成功后,使用Wagmi的useAccount钩子获取用户的钱包地址。为了提升用户体验,我做了地址缩写处理(显示前4位和后4位),并在页面顶部显示欢迎信息。这里还添加了一个复制地址的小功能,方便用户操作。 4. 链上数

【完整源码+数据集+部署教程】美食图像分割系统源码&数据集分享 [yolov8-seg-FocalModulation&yolov8-seg-GFPN等50+全套改进创新点发刊_一键训练教程_Web

【完整源码+数据集+部署教程】美食图像分割系统源码&数据集分享 [yolov8-seg-FocalModulation&yolov8-seg-GFPN等50+全套改进创新点发刊_一键训练教程_Web

背景意义 随着信息技术的迅猛发展,计算机视觉领域的研究逐渐深入,尤其是在图像分割和物体检测方面的应用越来越广泛。美食图像分割作为计算机视觉中的一个重要研究方向,不仅在餐饮行业、食品安全监测、营养分析等领域具有重要的实际应用价值,还为美食文化的传播和推广提供了新的技术手段。YOLO(You Only Look Once)系列模型以其高效的实时检测能力和较高的准确性,成为了图像分割领域的热门选择。特别是YOLOv8的推出,进一步提升了模型在复杂场景下的表现,使其在美食图像分割任务中展现出更为优越的性能。 本研究旨在基于改进的YOLOv8模型,构建一个高效的美食图像分割系统。我们使用的数据集包含3800张图像,涵盖136种不同类别的美食,包括各类主菜、配菜、调料等。这一丰富的类别设置为模型的训练和测试提供了良好的基础,能够有效提升模型对不同美食的识别能力。通过对美食图像的精确分割,不仅可以实现对美食的自动识别,还能够为后续的营养分析、热量计算和个性化推荐等应用提供数据支持。 在当今社会,健康饮食和营养均衡已成为人们日益关注的话题。通过美食图像分割技术,可以帮助用户更好地了解所摄取食物的