陈显江 發表於 2025-11-3 17:33:00

React 状态管理的“碎片化”

<h2 id="前言">前言</h2>
<p>三年前,我们还在 Reddit 上吵得不可开交:<br>
“Redux 太啰嗦!” “Zustand 太黑盒!” “Jotai 会内存泄漏!”</p>
<p>今天,React 19 直接把“外挂仓库”拆成了无数颗<strong>微状态胶囊</strong>(Micro-State Capsules)——随用随取,随丢随灭。<br>
<strong>状态不再集中,而是散落在组件树的最小粒度,靠框架自动合并、同步、持久化。</strong></p>
<h2 id="变化">变化</h2>
<table>
<thead>
<tr>
<th>场景</th>
<th>2022 痛点</th>
<th>2025 体感</th>
<th>补充示例</th>
</tr>
</thead>
<tbody>
<tr>
<td>数据请求</td>
<td>手写 <code>useEffect</code> + <code>swr</code>,缓存键泛滥</td>
<td><code>use(promise)</code> 同步写法,缓存自动化</td>
<td>见 3.1</td>
</tr>
<tr>
<td>全局主题</td>
<td><code>Context.Provider</code> 层层包裹,重渲染噩梦</td>
<td><code>use(style)</code> 原子化 CSS 变量,0 渲染成本</td>
<td>见 3.2</td>
</tr>
<tr>
<td>路由状态</td>
<td>路由库各自维护 <code>location</code>,跨页同步靠 hack</td>
<td><code>use(navigation)</code> 把路由当状态,页面间共享像 <code>useState</code></td>
<td>见 3.3</td>
</tr>
<tr>
<td>客户端持久化</td>
<td><code>zustand-persist</code> 手写版本号、迁移逻辑</td>
<td><code>use(storage)</code> 声明式注册,React 后台自动合并、压缩、迁移</td>
<td>见 3.4</td>
</tr>
</tbody>
</table>
<h2 id="实战">实战</h2>
<h3 id="31-数据请求3-行代码搞定拉取-缓存-重试">3.1 数据请求:3 行代码搞定“拉取-缓存-重试”</h3>
<pre><code class="language-tsx">// UserCard.tsx
export default function UserCard({ id }: { id: string }) {
// ① 接口返回 Promise,React 自动去重、缓存、过期重验证
const user = use(fetchUser(id));   // ← 同步写法,却具备 swr 全部能力

return (
    &lt;article&gt;
      &lt;h1&gt;{user.name}&lt;/h1&gt;
      &lt;img src={user.avatar} alt={user.name} /&gt;
    &lt;/article&gt;
);
}
</code></pre>
<p><strong>流程图:React 19 如何管理 use(promise)</strong></p>
<div class="mermaid">sequenceDiagram
组件-&gt;&gt;React: use(promise)
React-&gt;&gt;缓存: 命中?
alt 命中
    缓存--&gt;&gt;组件: 返回缓存数据
else 未命中
    React-&gt;&gt;服务端: 发起请求
    服务端--&gt;&gt;React: 数据
    React--&gt;&gt;缓存: 写入缓存
    React--&gt;&gt;组件: 返回数据
end
</div><h3 id="32-主题切换0-行-javascript-渲染逻辑">3.2 主题切换:0 行 JavaScript 渲染逻辑</h3>
<pre><code class="language-tsx">// DarkModeToggle.tsx
export default function DarkModeToggle() {
const = use(storage('theme', 'light'));

// 样式原子实时注入,不触发 React 渲染
use(style`
    :root {
      --bg: ${theme === 'dark' ? '#1e1e1e' : '#ffffff'};
      --fg: ${theme === 'dark' ? '#ffffff' : '#1e1e1e'};
    }
`);

return (
    &lt;button onClick={() =&gt; setTheme(t =&gt; t === 'dark' ? 'light' : 'dark')}&gt;
      {theme === 'dark' ? '☀️' : '🌙'}
    &lt;/button&gt;
);
}
</code></pre>
<p><strong>架构图:主题胶囊在浏览器各线程间的流向</strong></p>
<div class="mermaid">graph TD
A[组件setTheme] --&gt;|序列化| B
B --&gt;|BroadcastChannel| C(其他标签页)
B --&gt;|IDB| D(磁盘)
C --&gt;|重新读取| E
D --&gt;|下次加载| F
</div><h3 id="33-路由即状态语言切换不再刷新整页">3.3 路由即状态:语言切换不再刷新整页</h3>
<pre><code class="language-tsx">// LangSwitcher.tsx
export default function LangSwitcher() {
// 把 //blog 中的 lang 当成状态
const = use(navigation().param('lang'));

return (
    &lt;select value={lang} onChange={e =&gt; setLang(e.target.value)}&gt;
      &lt;option value="en"&gt;English&lt;/option&gt;
      &lt;option value="zh"&gt;中文&lt;/option&gt;
    &lt;/select&gt;
);
}
</code></pre>
<p><strong>关键点</strong></p>
<ul>
<li>改变 <code>lang</code> 等价于 <code>router.push</code>,但 Next.js 15 会<strong>只 RSC 渲染变更区域</strong>。</li>
<li>若同一页面有多语言段落,React 会流式返回 diff,<strong>首字节时间 &lt; 50 ms</strong>。</li>
</ul>
<h3 id="34-持久化迁移把-redux-store-搬进胶囊">3.4 持久化迁移:把 Redux Store 搬进“胶囊”</h3>
<p>假设你有一个旧 <code>userSlice</code> 结构:</p>
<pre><code class="language-ts">// legacy:userSlice
interface UserSlice {
id: string;
name: string;
vip: boolean;
}
</code></pre>
<p><strong>迁移 3 步曲</strong></p>
<ol>
<li>声明兼容类型</li>
</ol>
<pre><code class="language-ts">// migrate.ts
export const userCapsule = storage&lt;UserSlice&gt;('user', {
id: '',
name: '',
vip: false,
version: 1, // React 会根据 version 自动执行 migrate 函数
});
</code></pre>
<ol start="2">
<li>在根组件做一次“搬家中转”</li>
</ol>
<pre><code class="language-tsx">// App.tsx
function Bootstrap() {
const dispatch = useDispatch();
const legacyUser = useSelector(state =&gt; state.user);

// ② 把 Redux 数据写入胶囊,只需一次
const [, setUserCapsule] = use(userCapsule);
useEffect(() =&gt; setUserCapsule(legacyUser), );

return &lt;NextUI /&gt;;
}
</code></pre>
<ol start="3">
<li>业务组件直接订阅胶囊</li>
</ol>
<pre><code class="language-tsx">// UserBadge.tsx
export default function UserBadge() {
const user = use(userCapsule);   // ← 不再经过 Redux
return &lt;span&gt;{user.vip ? '👑' : '👤'} {user.name}&lt;/span&gt;;
}
</code></pre>
<p><strong>迁移流程图</strong></p>
<div class="mermaid">graph LR
    %% 节点定义
    ReduxStore["Redux Store"]
    Bootstrap["Bootstrap组件&lt;br/&gt;useSelector"]
    SetCapsule["setUserCapsule"]
    StorageWorker["Storage Worker"]
    IndexedDB[(IndexedDB)]
    Broadcast["BroadcastChannel"]
    OtherTab["其他标签页"]
    UserBadge["UserBadge组件&lt;br/&gt;use(userCapsule)"]

    %% 连线
    ReduxStore --&gt;|读取| Bootstrap
    Bootstrap --&gt;|写入| SetCapsule
    SetCapsule --&gt; StorageWorker
    StorageWorker --&gt;|持久化| IndexedDB
    StorageWorker --&gt;|同步| Broadcast
    Broadcast --&gt;|触发更新| OtherTab
    OtherTab --&gt;|读取| UserBadge
</div><h2 id="迁移">迁移</h2>
<ol>
<li><strong>渐进式切片</strong><br>
把 Redux Store 拆成页面级 slice → 封装 <code>toCapsule()</code> 转换函数 → 灰度 10 % 用户。</li>
<li><strong>双调度共存</strong><br>
旧组件 <code>createLegacyRoot()</code> 跑旧调度器,新组件 <code>createRoot()</code> 跑微状态调度器,通过 <code>useSyncExternalStore</code> 双向同步。</li>
<li><strong>类型即契约</strong><br>
一份 <code>GlobalState.d.ts</code> 映射旧字段 → TypeScript 自动提示“无人订阅”字段 → 安全删除。</li>
</ol>
<h2 id="结语">结语</h2>
<p>当缓存、持久化、路由、样式都被框架做成<strong>声明式原语</strong>,<br>
我们终于可以把 100% 的脑力放在<strong>产品逻辑</strong>而非“管数据”上。</p><br><br>
来源:https://www.cnblogs.com/guangzan/p/19187673
頁: [1]
查看完整版本: React 状态管理的“碎片化”