React Redux应用useEffect内状态初始化异常问题求助
React Redux 应用状态初始化与 localStorage 同步问题
应用状态结构
全局状态包含两个核心部分:
preferences.time:默认值0,存储用户设置的时间选项time.timerId:默认值null,存储定时器ID
Actions 定义
export const timerSet = (payload: number) => ({ type: TIMER_SET, payload }); export const setTime = (payload: number) => ({ type: SET_TIME, payload });
Reducer 实现
export const initialState: State = { preferences: { time: 0, }, time: { timer: 1, timerId: null, }, }; const timerReducer = ( state = initialState.time, { type, payload }: AnyAction ) => { switch (type) { case TIMER_SET: return { ...state, timer: payload }; default: return state; } }; const preferenceReducer = ( state = initialState.preferences, { type, payload }: AnyAction ) => { switch (type) { case SET_TIME: return { ...state, time: payload, }; default: return state; } };
Header 组件代码
export const Header = () => { const { preferences: { time }, time: { timer } } = useSelector((state: State) => state); const dispatch = useDispatch(); const [punctuation, setPunctuation] = useState(false); const [numbers, setNumbers] = useState(false); const [selectedOption, setSelectedOption] = useState(2); const [option, setOption] = useLocalStorage("time", time || 60); const timeOptions = useMemo(() => [ { id: 1, optionText: 15 }, { id: 2, optionText: 30 }, { id: 3, optionText: 60 }, { id: 4, optionText: 120 }, ], []); useEffect(() => { import(`../data/english.json`).then((words) => dispatch(setWordList(words)) ); dispatch(timerSet(option)); dispatch(setTime(option)); setSelectedOption(timeOptions.find(opt => opt.optionText === option)?.id || 2); }, [dispatch, option, timeOptions]); useEffect(() => { dispatch(setTime(option)); dispatch(timerSet(option)); resetTest() }, [dispatch, option]) return ( <div className={styles.Container}> <div className={styles.NavBar} style={{ width: "44%" }}> <h1 className={styles.NavText} onClick={() => setPunctuation(!punctuation)} style={punctuation ? { color: 'var(--main-color)' } : { color: 'var(--sub-color)' }}>@punctuation</h1> <h1 className={styles.NavText} onClick={() => setNumbers(!numbers)} style={numbers ? { color: 'var(--main-color)' } : { color: 'var(--sub-color)' }}>#numbers</h1> <div className={styles.Divider}></div> <h1 className={styles.NavText} style={{ color: 'var(--main-color)' }}>time</h1> <div className={styles.Divider}></div> {timeOptions.map((option) => ( <NavOption key={option.id} optionText={option.optionText} isSelected={option.id === selectedOption} onClick={() => { setSelectedOption(option.id); setOption(option.optionText); }} /> ))} </div> </div> ); }
resetTest 函数代码
export const resetTest = () => { const wordElements = document.getElementsByClassName("word"); if (wordElements.length > 0) { wordElements[0].scrollIntoView({ behavior: "smooth", block: "center" }); } const { dispatch, getState } = store; const { time: { timerId }, preferences: { time }, } = getState(); document .querySelectorAll(".wrong, .right") .forEach((el) => el.classList.remove("wrong", "right")); if (timerId) { clearInterval(timerId); dispatch(setTimerId(null)); } import(`../data/english.json`).then((words) => dispatch(setWordList(words.default)) ); dispatch(timerSet(time)); };
遇到的问题
- 页面初始化时,
preferences.time初始值为0而非localStorage中存储的值 timerId初始为null而非undefined,影响定时器清理逻辑- 将
resetTest()移至dispatch上方后,部分问题解决,但页面刷新时option先显示15,随后才更新为localStorage中的值
完整解决方案
1. 修正Redux初始状态,从localStorage预加载
在创建store时直接从localStorage读取初始值,避免组件初始化时的状态不一致:
const loadInitialState = () => { try { const savedTime = localStorage.getItem("time"); return { preferences: { time: savedTime ? parseInt(savedTime, 10) : 60, }, time: { timer: savedTime ? parseInt(savedTime, 10) : 60, timerId: undefined, }, }; } catch (e) { return { preferences: { time: 60, }, time: { timer: 60, timerId: undefined, }, }; } }; export const initialState: State = loadInitialState();
2. 优化Header组件的useLocalStorage与状态同步
useLocalStorage的初始值直接读取localStorage,不依赖Redux状态:
const savedTime = localStorage.getItem("time"); const [option, setOption] = useLocalStorage("time", savedTime ? parseInt(savedTime, 10) : 60);
合并重复的useEffect,避免多次dispatch:
useEffect(() => { import(`../data/english.json`).then((words) => dispatch(setWordList(words.default)) ); dispatch(setTime(option)); dispatch(timerSet(option)); setSelectedOption(timeOptions.find(opt => opt.optionText === option)?.id || 2); resetTest(dispatch, useSelector((state: State) => state)); }, [dispatch, option, timeOptions]);
3. 修正resetTest中的定时器逻辑
避免直接依赖store实例,改为传入dispatch和state参数:
export const resetTest = (dispatch: Dispatch, state: State) => { const wordElements = document.getElementsByClassName("word"); if (wordElements.length > 0) { wordElements[0].scrollIntoView({ behavior: "smooth", block: "center" }); } const { time: { timerId }, preferences: { time }, } = state; document.querySelectorAll(".wrong, .right").forEach((el) => el.classList.remove("wrong", "right")); if (timerId !== null && timerId !== undefined) { clearInterval(timerId); dispatch(setTimerId(undefined)); } import(`../data/english.json`).then((words) => dispatch(setWordList(words.default)) ); dispatch(timerSet(time)); };
4. 确保useLocalStorage实现正确
如果是自定义hook,保证挂载时立即读取localStorage:
export const useLocalStorage = <T>(key: string, initialValue: T) => { const [storedValue, setStoredValue] = useState<T>(() => { try { const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch (error) { console.error(error); return initialValue; } }); const setValue = (value: T | ((val: T) => T)) => { try { const valueToStore = value instanceof Function ? value(storedValue) : value; setStoredValue(valueToStore); window.localStorage.setItem(key, JSON.stringify(valueToStore)); } catch (error) { console.error(error); } }; return [storedValue, setValue] as const; };
内容的提问来源于stack exchange,提问作者Rohith Nambiar
相关产品推荐
相关产品推荐

