Loading...
Loading...
Compare original and translation side by side
useState(wholeObject)useState(整个对象)useState(snapshot)phasequiz.selectedWrongsetState(fullObject)App.tsxuseState(snapshot)phasequiz.selectedWrongsetState(完整对象)App.tsx@xstate/react@xstate/reactuseSelector(actor, selector)// GOOD: stable selector — re-renders only when phase changes
import { useSelector } from "@xstate/react";
import { selectPhase } from "./selectors";
function PhaseIndicator({ actor }) {
const phase = useSelector(actor, selectPhase);
return <Text>{phase}</Text>;
}// BAD: full snapshot in React state — re-renders on every actor change
const [snapshot, setSnapshot] = useState(null);
useEffect(() => {
const sub = actor.subscribe((snap) => setSnapshot(snap));
return () => sub.unsubscribe();
}, [actor]);
const phase = snapshot?.value?.sessionFlow; // unnecessary re-rendersuseStateuseSelectoruseRefconst [actor, setActor] = useState(() => {
const a = createActor(machine);
a.start();
return a;
});
const actorRef = useRef(actor);
actorRef.current = actor;
function send(event) {
actorRef.current.send(event);
}useSelector(actor, selector)// 推荐:稳定的选择器 —— 仅当phase变更时重渲染
import { useSelector } from "@xstate/react";
import { selectPhase } from "./selectors";
function PhaseIndicator({ actor }) {
const phase = useSelector(actor, selectPhase);
return <Text>{phase}</Text>;
}// 不推荐:将完整快照存入React状态 —— 每次actor变更都会重渲染
const [snapshot, setSnapshot] = useState(null);
useEffect(() => {
const sub = actor.subscribe((snap) => setSnapshot(snap));
return () => sub.unsubscribe();
}, [actor]);
const phase = snapshot?.value?.sessionFlow; // 不必要的重渲染useStateuseSelectoruseRefconst [actor, setActor] = useState(() => {
const a = createActor(machine);
a.start();
return a;
});
const actorRef = useRef(actor);
actorRef.current = actor;
function send(event) {
actorRef.current.send(event);
}@xstate/store-react@xstate/store-reactuseSelector(store, selector)compare// GOOD: select one field — re-renders only when count changes
import { createStore, useSelector } from "@xstate/store-react";
const store = createStore({
context: { count: 0, name: "" },
on: { inc: (ctx) => ({ ...ctx, count: ctx.count + 1 }) },
});
function CountDisplay() {
const count = useSelector(store, (state) => state.context.count);
return <span>{count}</span>;
}// BAD: selecting whole context — re-renders on any context change
const context = useSelector(store, (state) => state.context);
return <span>{context.count}</span>;const user = useSelector(
store,
(state) => state.context.user,
(prev, next) => prev.id === next.id
);useSelector(store, selector)compare// 推荐:选择单个字段 —— 仅当count变更时重渲染
import { createStore, useSelector } from "@xstate/store-react";
const store = createStore({
context: { count: 0, name: "" },
on: { inc: (ctx) => ({ ...ctx, count: ctx.count + 1 }) },
});
function CountDisplay() {
const count = useSelector(store, (state) => state.context.count);
return <span>{count}</span>;
}// 不推荐:选择整个上下文 —— 上下文任何变更都会触发重渲染
const context = useSelector(store, (state) => state.context);
return <span>{context.count}</span>;const user = useSelector(
store,
(state) => state.context.user,
(prev, next) => prev.id === next.id
);// GOOD: selector — re-renders only when count changes
const count = useStore((state) => state.count);
// GOOD: primitive or stable ref — minimal re-renders
const phase = useStore((state) => state.session.phase);// BAD: no selector — re-renders on every store change
const state = useStore();
return <span>{state.count}</span>;// BAD: selecting a new object every time — re-renders every time
const { count, name } = useStore((state) => ({ count: state.count, name: state.name }));
// Use two selectors or useShallow insteaduseShallowimport { useShallow } from "zustand/react/shallow";
const { count, name } = useStore(useShallow((state) => ({ count: state.count, name: state.name })));// 推荐:选择器 —— 仅当count变更时重渲染
const count = useStore((state) => state.count);
// 推荐:原始值或稳定引用 —— 最小化重渲染
const phase = useStore((state) => state.session.phase);// 不推荐:无选择器 —— 每次存储变更都会重渲染
const state = useStore();
return <span>{state.count}</span>;// 不推荐:每次都返回新对象 —— 每次都会重渲染
const { count, name } = useStore((state) => ({ count: state.count, name: state.name }));
// 改用两个选择器或useShallowuseShallowimport { useShallow } from "zustand/react/shallow";
const { count, name } = useStore(useShallow((state) => ({ count: state.count, name: state.name })));react-reduxreact-reduxuseSelector(selector)// GOOD: select a primitive or stable reference
const phase = useSelector((state) => state.session.phase);
const count = useSelector((state) => state.counter);// BAD: selecting whole slice — new object ref when any part of session updates
const session = useSelector((state) => state.session);
return <span>{session.phase}</span>;shallowEqualimport { shallowEqual, useSelector } from "react-redux";
const { phase, step } = useSelector(
(state) => ({ phase: state.session.phase, step: state.session.step }),
shallowEqual
);useSelector(selector)// 推荐:选择原始值或稳定引用
const phase = useSelector((state) => state.session.phase);
const count = useSelector((state) => state.counter);// 不推荐:选择整个片段 —— session任何部分更新都会生成新对象引用
const session = useSelector((state) => state.session);
return <span>{session.phase}</span>;shallowEqualimport { shallowEqual, useSelector } from "react-redux";
const { phase, step } = useSelector(
(state) => ({ phase: state.session.phase, step: state.session.step }),
shallowEqual
);@nanostores/react@nanostores/react// GOOD: one atom per logical slice, or computed for a derived slice
import { atom, computed } from "nanostores";
import { useStore } from "@nanostores/react";
const $session = atom({ phase: "idle", step: 0 });
const $phase = computed($session, (s) => s.phase);
function PhaseIndicator() {
const phase = useStore($phase); // re-renders only when phase changes
return <Text>{phase}</Text>;
}// BAD: one big store, useStore on the whole thing — re-renders on any change
const $app = atom({ session: {...}, quiz: {...}, ui: {...} });
function PhaseIndicator() {
const app = useStore($app);
return <Text>{app.session.phase}</Text>;
}useStore// 推荐:每个逻辑片段对应一个atom,或用computed派生片段
import { atom, computed } from "nanostores";
import { useStore } from "@nanostores/react";
const $session = atom({ phase: "idle", step: 0 });
const $phase = computed($session, (s) => s.phase);
function PhaseIndicator() {
const phase = useStore($phase); // 仅当phase变更时重渲染
return <Text>{phase}</Text>;
}// 不推荐:单个大型存储,订阅整个存储 —— 任何变更都会重渲染
const $app = atom({ session: {...}, quiz: {...}, ui: {...} });
function PhaseIndicator() {
const app = useStore($app);
return <Text>{app.session.phase}</Text>;
}useStore// GOOD: split by update frequency
<FrequentContext.Provider value={frequentData}>
<RareContext.Provider value={rareData}>
{children}
</RareContext.Provider>
</RareContext.Provider>// GOOD: store in context, select in consumer (e.g. Zustand store, XState actor)
function useSessionPhase() {
const store = useContext(StoreContext);
return useSelector(store, (s) => s.phase);
}// BAD: one context with everything — any change re-renders all consumers
<AppContext.Provider value={{ user, session, theme, settings, ... }}>// 推荐:按更新频率拆分
<FrequentContext.Provider value={frequentData}>
<RareContext.Provider value={rareData}>
{children}
</RareContext.Provider>
</FrequentContext.Provider>// 推荐:将存储放入Context,在消费者中选择状态(例如Zustand存储、XState角色)
function useSessionPhase() {
const store = useContext(StoreContext);
return useSelector(store, (s) => s.phase);
}// 不推荐:单个Context包含所有内容 —— 任何变更都会导致所有消费者重渲染
<AppContext.Provider value={{ user, session, theme, settings, ... }}>useSyncExternalStoregetSnapshot// GOOD: getSnapshot returns only the slice this component needs
const phase = useSyncExternalStore(
store.subscribe,
() => store.getSnapshot().session.phase,
() => store.getSnapshot().session.phase
);// BAD: getSnapshot returns full state — re-renders on every store change
const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
return <span>{state.session.phase}</span>;useSyncExternalStoregetSnapshot// 推荐:getSnapshot仅返回该组件所需的片段
const phase = useSyncExternalStore(
store.subscribe,
() => store.getSnapshot().session.phase,
() => store.getSnapshot().session.phase
);// 不推荐:getSnapshot返回完整状态 —— 每次存储变更都会重渲染
const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
return <span>{state.session.phase}</span>;// GOOD
const selectPhase = (snap) => snap.value?.sessionFlow;
function MyComponent({ actor }) {
const phase = useSelector(actor, selectPhase);
}
// BAD — new function ref every render
function MyComponent({ actor }) {
const phase = useSelector(actor, (snap) => snap.value?.sessionFlow);
}useMemo// 推荐
const selectPhase = (snap) => snap.value?.sessionFlow;
function MyComponent({ actor }) {
const phase = useSelector(actor, selectPhase);
}
// 不推荐 —— 每次渲染都会创建新函数引用
function MyComponent({ actor }) {
const phase = useSelector(actor, (snap) => snap.value?.sessionFlow);
}useMemo| Anti-pattern | Why it's bad | Fix |
|---|---|---|
| Every store/actor change re-renders | Use selector / slice (useSelector, selector arg, computed store) |
| No selector / whole store in hook | Same as above | Pass selector to useStore/useSelector; or use computed/small stores |
| Inline selector function | New reference each render | Module-level selector |
| Selector returns new object every time | Always re-renders | Return primitive or use shallowEqual/custom compare |
| Mega-context with everything | Any update re-renders all consumers | Split context or put a store in context and select in consumer |
| 反模式 | 危害 | 修复方案 |
|---|---|---|
在订阅中使用 | 每次存储/角色变更都会触发重渲染 | 使用选择器/状态片段(useSelector、选择器参数、computed存储) |
| Hook中无选择器/订阅整个存储 | 同上 | 为useStore/useSelector传入选择器;或使用computed/小型存储 |
| 内联选择器函数 | 每次渲染创建新引用 | 使用模块级选择器 |
| 选择器每次返回新对象 | 总是触发重渲染 | 返回原始值或使用shallowEqual/自定义比较 |
| 包含所有内容的巨型Context | 任何更新都会导致所有消费者重渲染 | 拆分Context,或在Context中放入存储并在消费者中选择状态 |
App.tsxApp.tsx