跳到主要内容
返回笔记
·769 字 / 约 2 分钟

单页应用里的「状态放哪里」其实只有三个答案

URL、组件树、全局 store —— 犹豫不决的时候,按这个顺序从上往下试,基本不会错。

前端React架构

结论先放前面

在 React 应用里决定一个状态该放哪里时,按这个优先级从上往下问自己:

优先级 位置 判断依据
1 URL 刷新/分享/后退之后,用户是否期望它还在?
2 组件树(就近) 只有一个子树关心它吗?
3 全局 store 以上都不满足,且跨多个不相邻的子树

1. URL 是第一候选

大部分"该不该上全局状态"的纠结,答案其实是 URL。

筛选条件、当前分页、选中的 Tab、打开的是哪条详情——这些状态天然属于 URL:

  • 用户刷新后希望它还在 ✅
  • 用户希望把链接发给别人 ✅
  • 浏览器后退键应该能撤销这次操作 ✅

放到 URL 里,这三个需求全部免费拿到。放到 store 里,你得自己实现一遍持久化和历史栈。

// 筛选条件放进 query string,而不是 useState + useEffect 同步
const [params, setParams] = useSearchParams()
const status = params.get('status') ?? 'all'

2. 就近放,别急着往上提

一个状态如果只被一个组件和它的子组件使用,那就让它待在最近的那个共同祖先里。

常见反模式是"以后可能会用到,先放 store 吧"。这个"以后"通常不会来,但它带来的是永久的额外复杂度:

  • 需要为它写 action、reducer、类型
  • 组件不再自洽,无法单独理解
  • 测试时需要先构造 store

等到真的需要共享时再提升,是成本更低的路径。重构一个 useState 到 store 是机械操作;反过来把 store 拆掉则要动一片。

3. 全局 store 要克制

真正需要全局状态的东西其实不多:

  • 用户身份与权限
  • 主题、语言这类贯穿整个应用外壳的偏好
  • 跨路由共享的草稿数据

选型上,我倾向 Zustand 而不是 Redux Toolkit——不是因为 Redux 不好,而是因为大部分项目根本不需要那么多约束。Zustand 的 store 就是一个 hook,心智负担小得多。

import { create } from 'zustand'
import { persist } from 'zustand/middleware'

export const useProfile = create(
  persist(
    (set) => ({
      name: '',
      setName: (name: string) => set({ name }),
    }),
    { name: 'profile' },
  ),
)

一个自检清单

拿不准的时候,问这几个问题:

  1. 刷新页面后它应该保留吗?→ URL
  2. 它能被"撤销"吗(后退键)?→ URL
  3. 只有一棵子树关心它吗?→ 就近 useState
  4. 它会在应用外壳层面被读到吗?→ store

四个都答"否",那这个状态可能压根不需要存在——它大概率是可以推导出来的派生值。

吾栖星球

个人非经营性网站。记录个人在软件开发、技术学习过程中的笔记与心得,并展示个人独立开发的非商业性小工具与作品集。

已记录 3 篇笔记 · 1 个作品

© 2026 吾栖星球 · 个人非经营性网站 · 自 2026

本站内容仅供学习交流,不构成任何商业服务冀ICP备2021005084号

这里是我的个人网站,非经营性、不提供任何商业服务,纯粹作为学习交流与自我沉淀的空间。