如何在Flutter的useEffect中监听Redux Store状态变化?
Flutter Hooks + Redux:为何useEffect无法监听状态变化?
问题场景
我在使用flutter_hooks和flutter_redux开发组件,代码如下:
class Root extends HookWidget { const Root({Key? key}) : super(key: key); @override Widget build(BuildContext context) { final store = StoreProvider.of<AppState>(context); store.onChange.listen((AppState appState) { print('store.onChange print: ${store.state.init}'); }); useEffect(() { print('useEffect print: ${store.state.init}'); store.dispatch({'init': true}); }, [store.state.init]); return Container(); } }
当前输出
useEffect print: false store.onChange print: true
期望输出
useEffect print: false store.onChange print: true useEffect print: true
为什么更新Store状态时store.onChange能触发,但useEffect钩子却没有触发?
React中的实现参考
在React里可以轻松实现类似逻辑,代码如下:
import React from "react"; import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { RootState } from "../../app/store"; import Box from "@mui/material/Box"; import { set } from "../../app/globalSlice"; const MyComponent = React.memo(() => { const dispatch = useAppDispatch(); const init = useAppSelector((state: RootState) => state.globalState.init); React.useEffect(() => { console.log("init state: " + init); if (init === false) { dispatch(set({ init: true })); } }, [init]); return <Box />; }); export default MyComponent;
React输出
init state: false init state: true
可以看到useEffect会随着init状态变化自动执行两次,无需手动触发组件更新。Flutter里有没有同样的实现方式?
问题原因
核心问题在于Flutter的HookWidget不会自动订阅Redux状态变化:
- React的
useAppSelector内部会订阅Redux store,当选中的状态字段变化时,会触发组件重新渲染,进而让useEffect检测到依赖变化并执行。 - 当前代码中,
store.state.init的变化不会触发Root组件的build方法重新执行,useEffect的依赖列表自然不会被重新校验,也就不会再次执行。 - 额外注意:Redux要求状态是不可变对象,如果
AppState在更新时直接修改原对象属性而非返回新实例,也会导致状态变化无法被正确感知。
解决方案
方案1:使用Redux Hooks封装(推荐)
可以使用flutter_redux_hooks库,或自行封装useSelector钩子,让组件订阅Redux状态的指定字段,状态变化时自动触发组件重建:
class Root extends HookWidget { const Root({Key? key}) : super(key: key); @override Widget build(BuildContext context) { // 用useSelector订阅init状态,状态变化时组件自动重建 final init = useSelector<AppState, bool>((state) => state.init); final store = StoreProvider.of<AppState>(context); useEffect(() { print('useEffect print: $init'); if (!init) { store.dispatch({'init': true}); } }, [init]); // 依赖init,变化时触发useEffect return Container(); } } // 自行封装useSelector简化版(无需第三方库) T useSelector<T>(T Function(AppState state) selector) { final store = StoreProvider.of<AppState>(useContext()); final selectedState = useState(selector(store.state)); useEffect(() { final subscription = store.onChange.listen((state) { final newSelected = selector(state); if (newSelected != selectedState.value) { selectedState.value = newSelected; } }); return subscription.cancel; }, [store]); return selectedState.value; }
方案2:手动在useEffect中监听store变化
如果不想引入第三方库或封装钩子,可直接在useEffect里监听store.onChange,在回调中处理状态变化逻辑:
class Root extends HookWidget { const Root({Key? key}) : super(key: key); @override Widget build(BuildContext context) { final store = StoreProvider.of<AppState>(context); useEffect(() { print('useEffect print: ${store.state.init}'); if (!store.state.init) { store.dispatch({'init': true}); } // 监听store变化,触发后续逻辑 final subscription = store.onChange.listen((appState) { print('useEffect 监听store变化: ${appState.init}'); // 此处可添加状态变化后的处理逻辑 }); // 组件销毁时取消订阅 return subscription.cancel; }, []); // 空依赖,只执行一次初始化 return Container(); } }
关键注意事项
确保AppState是不可变对象:每次更新状态时,必须返回新的AppState实例,而非修改原对象属性。示例如下:
AppState reducer(AppState state, dynamic action) { if (action is InitAction) { // 返回新实例,而非直接修改state.init = true return AppState(init: true); } return state; }
只有状态是不可变的,store.onChange才能正确捕获状态变化,组件也能感知到实例更新。
内容的提问来源于stack exchange,提问作者Lenny4
相关产品推荐
相关产品推荐

