React Hooks → Flutter 等价写法

React Hooks → Flutter 等价写法

适合 React 开发者快速上手 Flutter,直接从你熟悉的 Hooks 映射


总览对照表

React Hook Flutter 等价 说明
useState State 字段 + setState 响应式状态
useEffect(() => {}, []) initState 挂载时执行一次
useEffect(() => {}, [dep]) didUpdateWidget 依赖变化时执行
useEffect(() => { return () => {} }) dispose 卸载时清理
useRef State 字段(不调用 setState) 不触发重建的引用
useMemo const Widget / 手动缓存字段 缓存计算结果
useCallback State 类方法(天然稳定) 缓存函数引用
useContext InheritedWidget / Provider.of 跨层级读取数据
useReducer Bloc / 手写 reducer 复杂状态管理
useLayoutEffect addPostFrameCallback 渲染后同步执行
forwardRef GlobalKey 父组件访问子组件
React.memo const Widget 避免不必要重建
lazy + Suspense FutureBuilder 异步加载
createPortal Overlay 渲染到根层

一、useState → State 字段 + setState

jsx 复制代码
// React
const [count, setCount] = useState(0);
const [name, setName] = useState('');
const [user, setUser] = useState(null);

setCount(c => c + 1);  // 函数式更新
dart 复制代码
// Flutter
class _MyState extends State<MyWidget> {
  int count = 0;
  String name = '';
  User? user;          // null 用 ? 表示可空

  void increment() {
    setState(() {
      count += 1;      // 直接修改,无需函数式更新
    });
  }
}

差异 :Flutter 直接修改字段值,不需要 prev => prev + 1 这种函数式更新。 setState 的作用只是通知框架重建,不负责计算新值。


二、useEffect → initState / didUpdateWidget / dispose

2.1 挂载时执行一次(依赖数组为空 []

jsx 复制代码
// React
useEffect(() => {
  fetchData();
  startTimer();
}, []);
dart 复制代码
// Flutter
@override
void initState() {
  super.initState();
  fetchData();
  startTimer();
}

2.2 依赖变化时执行(依赖数组有值 [dep]

jsx 复制代码
// React
useEffect(() => {
  fetchUser(userId);
}, [userId]);
dart 复制代码
// Flutter
@override
void didUpdateWidget(covariant MyWidget oldWidget) {
  super.didUpdateWidget(oldWidget);
  // 对比新旧 widget 属性,手动判断是否变化
  if (oldWidget.userId != widget.userId) {
    fetchUser(widget.userId);
  }
}

2.3 卸载时清理(return 清理函数)

jsx 复制代码
// React
useEffect(() => {
  final sub = stream.listen(handler);
  return () => sub.cancel();  // 清理
}, []);
dart 复制代码
// Flutter
StreamSubscription? _sub;

@override
void initState() {
  super.initState();
  _sub = stream.listen(handler);
}

@override
void dispose() {
  _sub?.cancel();  // 清理
  super.dispose();
}

2.4 每次 render 后执行(无依赖数组)

jsx 复制代码
// React
useEffect(() => {
  console.log('每次渲染后执行');
});
dart 复制代码
// Flutter ------ 不推荐,但可以在 build 末尾用 addPostFrameCallback
@override
Widget build(BuildContext context) {
  WidgetsBinding.instance.addPostFrameCallback((_) {
    // 每次 build 完成后执行
  });
  return ...;
}

三、useRef → 普通字段(不触发重建)

jsx 复制代码
// React
const timerRef = useRef(null);
const inputRef = useRef(null);

timerRef.current = setTimeout(...);
inputRef.current.focus();
dart 复制代码
// Flutter
// 普通字段就是 useRef,修改它不触发 setState 就不会重建
Timer? _timer;
final FocusNode _focusNode = FocusNode();

// 访问 Widget 实例(等价于 inputRef.current)
final GlobalKey _key = GlobalKey();

规律 :React 需要 useRef 是因为函数组件每次 render 都会重新声明变量。 Flutter 的 State 类只创建一次,普通字段天然就是"ref"。


四、useMemo → const Widget / 缓存字段

jsx 复制代码
// React
const expensiveValue = useMemo(() => {
  return heavyCalculation(data);
}, [data]);

const element = useMemo(() => <ExpensiveComponent />, []);
dart 复制代码
// Flutter

// 缓存计算结果:手动在 initState 或 didUpdateWidget 里计算并存字段
List<Item> _sorted = [];

@override
void initState() {
  super.initState();
  _sorted = [...widget.items]..sort();  // 只算一次
}

// 缓存 Widget:用 const,编译期复用,父 build 重建不影响它
const ExpensiveWidget()   // ✅ 等价于 useMemo(() => <Component />, [])

五、useCallback → State 类方法(天然稳定)

jsx 复制代码
// React ------ 每次 render 函数引用会变,需要 useCallback 稳定引用
const handleClick = useCallback(() => {
  setCount(c => c + 1);
}, []);
dart 复制代码
// Flutter ------ 方法定义在 State 类里,引用天然稳定,不需要任何处理
void _handleClick() {
  setState(() => count++);
}

// 使用
TextButton(onPressed: _handleClick, child: Text('点击'))

差异:Flutter 完全不存在这个问题,State 类方法不会在每次 build 时重新创建。


六、useContext → Provider.of / context.read

jsx 复制代码
// React
const theme = useContext(ThemeContext);
const { user, setUser } = useContext(UserContext);
dart 复制代码
// Flutter(以 Provider 包为例,对应 React Context)

// 读取并订阅(数据变化时重建,等价于 useContext)
final theme = context.watch<ThemeModel>();

// 只读取一次,不订阅(等价于 useRef 读 context,不触发重建)
final user = context.read<UserModel>();

// 调用方法
context.read<UserModel>().logout();

七、useReducer → Bloc / 手写 reducer

jsx 复制代码
// React
const reducer = (state, action) => {
  switch (action.type) {
    case 'increment': return { ...state, count: state.count + 1 };
    case 'reset':     return { count: 0 };
  }
};
const [state, dispatch] = useReducer(reducer, { count: 0 });
dispatch({ type: 'increment' });
dart 复制代码
// Flutter ------ 手写 reducer(轻量场景)
class CountState {
  final int count;
  const CountState(this.count);
}

CountState reducer(CountState state, String action) {
  switch (action) {
    case 'increment': return CountState(state.count + 1);
    case 'reset':     return const CountState(0);
    default:          return state;
  }
}

class _MyState extends State<MyWidget> {
  CountState _state = const CountState(0);

  void dispatch(String action) {
    setState(() => _state = reducer(_state, action));
  }
}

八、useLayoutEffect → addPostFrameCallback

jsx 复制代码
// React ------ DOM 更新后同步执行,获取元素尺寸
useLayoutEffect(() => {
  const rect = ref.current.getBoundingClientRect();
  setHeight(rect.height);
}, []);
dart 复制代码
// Flutter ------ 帧渲染完成后执行,获取 Widget 尺寸
@override
void initState() {
  super.initState();
  WidgetsBinding.instance.addPostFrameCallback((_) {
    final box = _key.currentContext?.findRenderObject() as RenderBox?;
    final size = box?.size;
    setState(() => _height = size?.height ?? 0);
  });
}

九、React.memo → const Widget

jsx 复制代码
// React ------ 父组件重渲染时,props 没变就跳过
const Child = React.memo(({ title }) => <div>{title}</div>);
dart 复制代码
// Flutter ------ 方式1:const(最简单,适合静态内容)
const MyWidget()   // 编译期确定,父 build 重建时直接复用

// Flutter ------ 方式2:不依赖外部变化的子 Widget 天然不重建
// Flutter 的 diff 算法:类型相同 + key 相同 = 复用 State,不重新创建

十、整体对比:组件生命周期

scss 复制代码
React 函数组件                    Flutter StatefulWidget
─────────────────────────────────────────────────────────
渲染阶段:
  function Component()         ←→   Widget build(context)

挂载:
  useEffect(() => {}, [])      ←→   initState()

依赖更新:
  useEffect(() => {}, [dep])   ←→   didUpdateWidget()

Context 变化:
  useContext 自动订阅           ←→   didChangeDependencies()

卸载:
  useEffect(() => () => {})    ←→   dispose()

每帧渲染后:
  useLayoutEffect              ←→   addPostFrameCallback

十一、常见陷阱对比

场景 React 陷阱 Flutter 等价陷阱
闭包捕获旧值 useEffect 依赖数组漏写 didUpdateWidget 忘记判断新旧值
内存泄漏 忘记清理订阅 dispose 里忘记 cancel/dispose
死循环 useEffectsetState 未加条件 build 里直接调用 setState
异步 setState 组件卸载后 setState dispose 后调用 setState(会报错)
不必要重建 未用 memo / useCallback 未用 const / RepaintBoundary
相关推荐
最爱睡觉睡觉睡觉1 小时前
CSS → Flutter 对照手册
android·前端
xiaofeichaichai1 小时前
Service Worker、PWA 与 Web Worker — 离线缓存与主线程算力分离
前端·缓存
JustHappy1 小时前
古法编程秘籍(四):函数究竟是什么?把函数最重要的能力一次讲清楚
前端·后端·面试
OpenTiny社区1 小时前
一行命令添加 AI 对话入口!TinyRobot 也太省事了~
前端·vue.js·ai编程
sagima_sdu1 小时前
Vue 前端径向渐变背景制作
前端·javascript·vue.js
叶落阁主2 小时前
Vue3 后台管理系统全局菜单搜索实战:Cmd/Ctrl + K、权限菜单与拼音过滤
前端·javascript·vue.js
卷帘依旧2 小时前
setState是同步的还是异步的
前端·面试
卷帘依旧2 小时前
讲一下useEffect和useLayoutEffect
前端·面试
wuhen_n2 小时前
AI Agent 入门:从零实现 LangChain 基础智能体
前端·langchain·ai编程