Flutter 真的非 Hooks 不可吗?聊聊我的思考
前言:别因为“大家说好”就去用
对于三方库,我一贯的态度是:不能因为别人吹得神乎其神你就跟着上。得先搞清楚它好在哪、适不适合你的项目,以及——如果不吃这颗药,你的病是不是真的治不好?
我最近又仔细研究了一下,Hooks 最大的价值其实在于它的声明式状态同步。因为它直接写在 build 方法里,每次重绘都能拿到最新值,这确实非常契合 Flutter 的声明式 UI 逻辑。
Hooks 解决的核心痛点:参数实时同步
1. 表面上的便利:帮开发者“偷懒”
很多人用 Hooks 是为了少写点 dispose 或者 didUpdateWidget 的样板代码。
// 原生方式:逻辑散落在各处,维护起来很头大
class _AnimatedBoxState extends State<AnimatedBox> with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(duration: widget.duration, vsync: this);
_controller.addStatusListener(_onComplete);
}
@override
void didUpdateWidget(AnimatedBox oldWidget) {
super.didUpdateWidget(oldWidget);
// 只要参数一多,这里就是一长串的 if 判断,漏写一个就是 Bug
if (widget.duration != oldWidget.duration) {
_controller.duration = widget.duration;
}
if (widget.onComplete != oldWidget.onComplete) {
_controller.removeStatusListener(_onComplete);
_controller.addStatusListener(_onComplete);
}
}
@override
void dispose() {
_controller.removeStatusListener(_onComplete);
_controller.dispose();
super.dispose();
}
}
// Hooks 方式:一眼看穿逻辑
Widget build(BuildContext context) {
final controller = useAnimationController(duration: duration);
useEffect(() {
controller.addStatusListener(onComplete);
return () => controller.removeStatusListener(onComplete);
}, [onComplete]);
}
这种差异在写基础 UI 组件时特别明显,因为这类组件参数多,手动同步状态简直是噩梦。
但说实话,didUpdateWidget 里面如果参数特别多,封装个 class 就行了,这种参数爆多需要更新的场景真的很少。
2. 核心竞争力:声明式的“值映射”
Hooks 真正厉害的地方,是它把“创建”和“更新”统一了。
- 原生模式(打补丁) :
initState负责生,didUpdateWidget负责改。你得像个保姆一样,显式地告诉控制器:“喂,外面的参数变了,你赶紧也更新一下”。万一哪天新加个参数忘了同步,逻辑就断层了。 - Hooks 模式(声明式) :你只需要告诉系统“我要用这个参数”,Hooks 内部会自动判断是该初始化还是该更新属性。
// 声明即同步:管你传进来的是新值还是旧值,我拿到的永远是正确的
final controller = useAnimationController(duration: duration);
3. 复用逻辑:Mixin 还是 Hooks?
首先要说句公道话:Mixin 完全能搞定逻辑复用。 Flutter 官方到处都在用 Mixin(比如 TickerProviderStateMixin),它很成熟。
但两者的代码组织哲学完全不同:
| 特性 | Mixin | Hooks |
|---|---|---|
| 思维方式 | 面向对象(混入/继承) | 函数式(组合/调用) |
| 状态管理 | 将逻辑“塞进”类里,手动协调 | 将逻辑封装成“函数”,自动管理 |
| 易用性 | 适合习惯 OOP 的人 | 适合需要频繁组合逻辑的场景 |
他们仅仅是实现思想不同,逻辑复用上 mixin 和 hooks 都可以。
为什么 Flutter 没像 React 那样“非 Hooks 不可”?
React 拥抱 Hooks 是因为类组件实在太难用了,但 Flutter 环境不太一样:
- Dart 没有
this的坑:JS 的this绑定能让人抓狂,而 Dart 里的方法引用非常直观,没那么多历史包袱。 - 生命周期相对清晰:Flutter 的
initState、didUpdateWidget职责分明。虽然写起来繁琐,但逻辑不乱。 - 心智负担:Hooks 有一套严格的“潜规则”(不能写在 if 里,不能写在循环里)。对于习惯了声明式 Widget 的人来说,这种“顺序敏感”的逻辑确实需要点时间适应。
Compose 和 SwiftUI:天生就是 Hooks 思维
有意思的是,Jetpack Compose 和 SwiftUI 这两个后起之秀,从设计之初就深度整合了 Hooks 的核心思想。
Jetpack Compose (Kotlin)
@Composable
fun AnimatedBox(duration: Duration, onComplete: () -> Unit) {
// 直接在 Composable 函数里声明状态,自动跟踪依赖
val animatable = remember { Animatable(0f) }
// LaunchedEffect 相当于 useEffect,依赖变化时自动重启
LaunchedEffect(duration, onComplete) {
animatable.animateTo(1f, animationSpec = tween(duration))
onComplete()
}
}
SwiftUI (Swift)
struct AnimatedBox: View {
let duration: Double let onComplete: () -> Void
// @State 自动管理状态生命周期
@State private var progress: CGFloat = 0
var body: some View {
Rectangle() .onAppear {
withAnimation(.easeIn(duration: duration)) {
progress = 1.0
}
}
// onChange 监听依赖变化,类似 useEffect
.onChange(of: duration) {
newValue in
// 自动响应参数变化
}
}
}
为什么它们天然适合 Hooks?
- 纯函数设计:Compose 和 SwiftUI 的组件都是纯函数(Composable/@ViewBuilder),没有类的包袱,状态管理天然就是"声明式"的。
- 编译器加持:Kotlin 的编译器插件和 Swift 的 Property Wrapper 能在编译期做很多魔法,让 Hooks 的使用体验更丝滑(比如自动追踪依赖)。
- 没有历史包袱:它们不需要像 Flutter 那样兼容已有的 StatefulWidget 体系,可以从零开始设计最适合声明式 UI 的 API。
Flutter 需要一次大版本进化,从 class Widget 进化到 function Widget 编译器上再增加些支持,才能完美契合 hooks
什么时候该上 Hooks?
强烈建议用的时候:
- didUpdateWidget 里面有一堆参数要更新。
- 就是喜欢追潮流,追时尚,挡不住
可以不用的时候:
- 业务逻辑都在 ViewModel 里:如果你用了 Provider/Bloc 并且 UI 层只是简单渲染,Hooks 意义不大。
- 简单的展示页面:几个文字图片,搞 Hooks 属于杀鸡用牛刀。
- 团队还没准备好:如果大家对函数式编程不感冒,强行引入只会增加沟通成本。
最后的结论
Hooks 并不是“更好”的替代品,它只是提供了一种不同的解题思路。