React Zustand 学习笔记(对照Vue3)
React & Zustand 学习笔记
Codex和Claude Coder这些viber coding似乎更加偏爱react的技术栈,既然这些AI根喜欢,那我们就迁就他,能少写点bug就好。所以对比vue3问了codex一些问题帮助在有vue基础的情况下快速理解react,这里把笔记贴出来方便复习。
React 核心理念
- UI = f(state):组件是纯函数,传入相同的 props/state 总能得到相同的 UI。
- Hooks(
useState
、useEffect
、useMemo
等)提供状态、生命周期、副作用管理,函数组件无需类即可掌握全部能力。 - Fiber 架构把渲染拆成可中断/恢复的任务,每个 Fiber 节点携带 props、state、effectTag 等信息,协调器根据优先级安排重渲与副作用提交。
- Diff 机制会比较新旧虚拟 DOM,只把真实 DOM 中的差异打补丁,保持渲染高效。
JSX 速览
const button = <button className="primary">Click</button>;
.tsx
/.jsx
文件 +tsconfig.json
配置jsx: "react-jsx"
后,编译器会把 JSX 转成React.createElement
调用。- 小写标签映射到原生元素,大写代表自定义组件。
- TypeScript 会根据组件 props/children 做类型检查。
React 状态管理
局部状态:useState
- 组件里调用
useState
会在当前 Fiber 上注册一个 Hook 节点,记录memoizedState
和queue
。 setState
会把 update 放进 queue,并标记 Fiber 需要更新;下一次渲染重新执行组件函数,生成新的 UI。
全局状态:Zustand
createWithEqualityFn
返回的useStore
Hook 把外部 store 接入 React,底层使用useSyncExternalStoreWithSelector
。- Store 初始化函数
(set, get) => ({ ... })
中的set
/get
由 Zustand 注入,分别负责写入和读取最新状态。 useCounterStore((state) => state.count)
中的箭头函数是 selector,只订阅所需切片;Zustand 比较 selector 新旧结果,不变就跳过组件重渲。- 想要广播更新,必须调用 store 暴露的 action(例如
increment
)或setState
,单纯修改本地变量不会同步。
示例:Zustand 计数器
// counterStore.ts
import { createWithEqualityFn } from "zustand/traditional";interface CounterState {count: number;step: number;increment: () => void;reset: () => void;
}export const useCounterStore = createWithEqualityFn<CounterState>()((set, get) => ({count: 0,step: 1,increment: () => set(({ count, step }) => ({ count: count + step })),reset: () => set({ count: 0 }),
}));
// FastCounter.tsx
import { memo } from "react";
import { useCounterStore } from "../store/counterStore";export const FastCounter = memo(() => {const count = useCounterStore((state) => state.count);const increment = useCounterStore((state) => state.increment);return (<div><h1>Count: {count}</h1><button onClick={increment}>+1</button></div>);
});
React 渲染与订阅
useSyncExternalStore
是 React 18 提供的官方订阅 API。subscribe(onStoreChange)
注册监听;React 在 commit 阶段调用订阅函数。getSnapshot()
同步读取当前快照;React 在每次重渲前后用Object.is
对比新旧值。- SSR 下可选
getServerSnapshot()
,保持水合一致。
useSyncExternalStoreWithSelector
额外支持 selector/equalityFn,Zustand、Redux Hooks 都基于它实现。- 组件里的
useState
/useReducer
更新也是同样流程:setState → 在 Fiber 上挂 update → 调度器重新执行组件函数 → diff → commit。
React vs Vue 对比
- 语法:Vue SFC 模板 + 指令;React 使用 JSX,逻辑与视图合体。
- 状态模型:Vue 基于响应式 Proxy 自动追踪依赖;React 采用显式
setState
,依靠虚拟 DOM diff 获取变化。 - 组件通信:Vue 提供 props/emit,也可传函数;React 统一用 props/回调 + 状态提升。
- 生态:Vue 有 Pinia、Router 官方支持;React 生态更去中心化,需要自行选择 Zustand/Redux、React Router 等。
- 双向绑定:Vue 有
v-model
;React 通过受控组件(value + onChange)模拟,仍是单向数据流。
React 与 Vue 迁移提示
- 可以把 hooks 看成 Vue 的组合式 API:
useState
≈ref
,useEffect
≈watchEffect
/生命周期,useMemo
≈computed
。 - 父组件感知子组件变化通常传入回调函数,不存在内置
emit
,但本质和 Vue 传函数 props 类似。
SSR(Server-Side Rendering)
- 在服务器预先渲染 HTML,客户端接收后再 hydrate,提升首屏速度与 SEO。
useSyncExternalStore
的getServerSnapshot
参数帮助在 SSR 中提供一致快照,避免水合警告。
常见问答备忘
- React 会自动追踪普通变量吗? 不会,必须通过
setState
或 store action 明确触发更新。 - Zustand selector 返回的值可直接修改吗? 不可,它只是一个快照;需要调用 action 更新。
set
会导致循环更新吗? 不会,set
内部只执行一次:读取旧值 → 计算新值 → 写入 → 通知订阅者,除非你在 effect 中再次触发 set。- Fiber 和 Vue VNode 一样吗? Fiber 不仅描述节点,还存储调度信息(优先级、更新队列、effectTag),用于实现可中断渲染。
React/Vue 对照小示例
React Counter(受控 + 历史)
import { useState, useEffect } from "react";export function Counter({ initial = 0, step = 1 }: { initial?: number; step?: number }) {const [count, setCount] = useState(initial);const [history, setHistory] = useState<number[]>([]);useEffect(() => {setHistory((prev) => [...prev, count]);}, [count]);return (<section><h1>Count: {count}</h1><button onClick={() => setCount((prev) => prev + step)}>+{step}</button><button onClick={() => { setCount(initial); setHistory([]); }}>Reset</button><p>History: {history.join(" → ") || "Empty"}</p></section>);
}
Vue 3 Composition API 等价实现
<script setup lang="ts">
import { ref, watch, withDefaults, defineProps } from "vue";interface Props { initial?: number; step?: number; }
const props = withDefaults(defineProps<Props>(), { initial: 0, step: 1 });const count = ref(props.initial);
const history = ref<number[]>([]);watch(count, (value) => {history.value = [...history.value, value];
});const handleIncrement = () => { count.value += props.step; };
const handleReset = () => { count.value = props.initial; history.value = []; };
</script><template><section><h1>Count: {{ count }}</h1><button @click="handleIncrement">+{{ props.step }}</button><button @click="handleReset">Reset</button><p>History: {{ history.length ? history.join(' → ') : 'Empty' }}</p></section>
</template>
术语速记
- Hook:函数组件里调用的 React API,例如
useState
、useEffect
。 - Fiber:React 的内部数据结构,表示组件节点并携带调度信息。
- Selector:在 Zustand/Redux 中用于挑选 store 切片的函数。
- Hydration:SSR 输出的 HTML 在客户端由 React/Vue 接管的过程。
useSyncExternalStore
:把外部数据源接入 React 渲染流程的官方 Hook。