Redux Toolkit中定义依赖其他状态属性的状态的正确方式
在Redux Toolkit中处理依赖其他状态的派生属性
Redux的核心原则是只存储原始数据源,像typedChars、remainingChars这种依赖其他状态的派生属性,不建议直接存在state里,而是通过**Selector(选择器)**实时计算。这种方式既能避免props drilling,又能保证状态一致性。
具体实现步骤
1. 精简初始状态,只存原始数据
保持slice初始状态只包含核心原始数据,去掉需要计算的派生属性:
interface AppSliceState { words: string; cursor: number; } const initialState: AppSliceState = { words: "", cursor: 0, };
2. 创建Selector计算派生状态
在slice文件中定义Selector,从原始state中计算出目标派生属性。推荐使用Redux Toolkit提供的createSelector,它会缓存计算结果,只有当依赖的原始状态变化时才重新计算,优化性能:
import { createSelector } from '@reduxjs/toolkit'; import { RootState } from '../store'; // 导入你的根状态类型 // 基础Selector:获取当前slice的状态 const selectAppSlice = (state: RootState) => state.app; // 计算已输入字符 export const selectTypedChars = createSelector( [selectAppSlice], (appState) => appState.words.substring(0, appState.cursor) ); // 计算剩余字符 export const selectRemainingChars = createSelector( [selectAppSlice], (appState) => appState.words.substring(appState.cursor) );
如果是简单场景,也可以直接写普通函数式Selector(无缓存,适合计算成本低的场景):
export const selectTypedChars = (state: RootState) => state.app.words.substring(0, state.app.cursor); export const selectRemainingChars = (state: RootState) => state.app.words.substring(state.app.cursor);
3. 在组件中使用Selector获取派生状态
任何需要这两个值的组件,直接通过useSelector钩子获取即可,完全不需要props传递:
import { useSelector } from 'react-redux'; import { selectTypedChars, selectRemainingChars } from './appSlice'; function TypingDisplay() { const typedChars = useSelector(selectTypedChars); const remainingChars = useSelector(selectRemainingChars); return ( <div className="typing-area"> <span className="typed">{typedChars}</span> <span className="remaining">{remainingChars}</span> </div> ); }
不推荐直接存储派生状态的原因
如果强行把typedChars和remainingChars存在state里,会带来以下问题:
- 状态冗余:原始的
words和cursor已经包含了所有计算所需信息,存储派生状态属于重复数据 - 一致性风险:每次
words或cursor更新时,都要手动同步更新这两个派生属性,一旦遗漏就会出现状态不一致 - 维护成本高:额外增加了需要维护的状态属性,逻辑复杂度上升
特殊场景的妥协方案
如果你的派生状态计算逻辑极复杂、且多个组件频繁调用导致性能问题,可以考虑把派生状态存在state中,但必须确保每次依赖状态变化时同步更新:
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; const appSlice = createSlice({ name: 'app', initialState: { words: "", cursor: 0, typedChars: "", remainingChars: "", } as AppSliceState, reducers: { setCursor: (state, action: PayloadAction<number>) => { state.cursor = action.payload; // 同步更新派生状态 state.typedChars = state.words.substring(0, state.cursor); state.remainingChars = state.words.substring(state.cursor); }, }, extraReducers: (builder) => { // 假设fetchWords是获取文本的异步action builder.addCase(fetchWords.fulfilled, (state, action) => { state.words = action.payload; // 同步更新派生状态 state.typedChars = state.words.substring(0, state.cursor); state.remainingChars = state.words.substring(state.cursor); }); }, });
但这种方式仅作为性能优化的最后手段,优先推荐使用Selector方案。
内容的提问来源于stack exchange,提问作者Altyn
相关产品推荐
相关产品推荐

