Skip to content

Flutter组件的依赖关系

· 9 min

“组件依赖关系”到底是个啥?#

先说人话:在 Flutter 里,组件(Widget)要么“吃”来自祖先的东西(比如主题、屏幕尺寸、状态),要么被爸爸妈妈(父组件)管着怎么摆放、怎么长大(布局约束)。这些“吃东西”和“被管束”的过程,就是组件的依赖关系。你一旦明白谁依赖谁、怎么建立依赖、依赖变了会发生啥,写出来的界面就不容易“抽风”,性能还更稳。

三棵树,别搞混#

Flutter UI 运行时,背后有三棵树:

依赖关系绝大多数时候,都是 Element 层在“登记备案”。比如你在 build 里通过 Theme.of(context) 拿主题,实际上是 Element 在对上游的 InheritedWidget 建立“监听关系”。

依赖的两条主线#

InheritedWidget:监听是怎么“挂上去”的?#

当你在 build 里调用:

final theme = Theme.of(context);

本质是调用了 context.dependOnInheritedWidgetOfExactType<Theme>()。这一步会:

之后只要这个 ThemeupdateShouldNotify 返回了 true,所有“在听”的孩子就会被标记重建。
注意还有个“只拿不听”的方式:

context.getElementForInheritedWidgetOfExactType<Theme>();

或第三方状态工具里的 read,意思是:就取一次,不建立依赖,别给我重建。

生命周期:依赖关系在什么时候能用?#

常见依赖来源和写法对比#

小例子:手写一个简单依赖#

import 'package:flutter/material.dart';
class CounterProvider extends InheritedWidget {
const CounterProvider({
super.key,
required this.count,
required super.child,
});
final int count;
static CounterProvider of(BuildContext context) {
final provider = context.dependOnInheritedWidgetOfExactType<CounterProvider>();
assert(provider != null, 'no CounterProvider found in context');
return provider!;
}
@override
bool updateShouldNotify(CounterProvider oldWidget) => count != oldWidget.count;
}
class CounterPage extends StatefulWidget {
const CounterPage({super.key});
@override
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int count = 0;
@override
Widget build(BuildContext context) {
return CounterProvider(
count: count,
child: Scaffold(
appBar: AppBar(title: const Text('依赖关系示例')),
body: const Center(child: CountText()),
floatingActionButton: FloatingActionButton(
onPressed: () => setState(() => count++),
child: const Icon(Icons.add),
),
),
);
}
}
class CountText extends StatelessWidget {
const CountText({super.key});
@override
Widget build(BuildContext context) {
final c = CounterProvider.of(context).count; // 建立依赖
return Text('当前 count: $c');
}
}

这段里,CountTextCounterProvider 建立了依赖。只要 count 改变,updateShouldNotify 返回 trueCountText 就会重建。
如果你只想读一次不监听,可以用 context.getElementForInheritedWidgetOfExactType 先拿 Element,再取 widget,不过一般结合状态管理框架会更顺手。

布局依赖:父子之间的“规矩”#

Flutter 的布局协议是这样的:

这是一种“隐式依赖”:子必须在父给的规则里活动。你也能看到一些“依赖链”的现象,比如 Expanded/Flexible 依赖 Row/Column 的分配策略,Sliver 系列依赖滚动容器的约束节拍。

哪些地方容易踩坑?#

性能小妙招:把“听众”做小#

生命周期再捋一遍#

心智模型:记住这三句话#

一个更接地气的 Provider 对照#

// 监听:数据变动时重建
final user = context.watch<User>();
// 只读一次:初始化拉接口用
final repo = context.read<UserRepository>();
// 精准监听:只关心 name 字段变不变
final name = context.select<User, String>((u) => u.name);

这三种,决定了“有没有建立依赖”、“依赖的粒度有多细”。听谁、听哪部分、什么时候听,完全由你拿捏。

收尾:写 Flutter,别跟依赖“怄气”#

组件依赖关系并不玄学:

把这些搞顺了,UI 就像上了油的拉面,筋道还不粘锅。等你下次看到一大片莫名其妙的重建,不妨先问一句:我到底在听谁?我是不是听多了?听对了没?

——写到这,我已经迫不及待把几个“爱瞎听”的组件拆小了。你也试试,准保见效。