Skip to content

组件通信

在 Flutter 中,组件(Widget)之间的数据传递和事件通知是构建应用的核心能力。本文汇总 Flutter 中常见的组件通信方式,帮助你选择合适的方案。

通信方式概览

方式方向适用场景复杂度
构造函数参数父 → 子传递展示数据
回调函数子 → 父子组件通知父组件事件
State 回调 + 状态提升子 ↔ 父父组件管理状态,子组件触发修改⭐⭐
InheritedWidget上 → 下(跨层级)多层子树共享数据⭐⭐⭐
Provider上 → 下(跨层级)推荐的跨组件状态共享方案⭐⭐
ValueNotifier / ChangeNotifier任意方向轻量级响应式通知⭐⭐
GlobalKey任意方向跨组件访问 State⭐⭐⭐
EventBus任意方向解耦的跨组件事件通信⭐⭐⭐

父传子:构造函数参数

这是最基础、最常用的通信方式。父组件通过子组件的构造函数参数将数据传递给子组件。

基本用法

dart
// 子组件 —— 通过构造函数接收数据
class UserAvatar extends StatelessWidget {
  const UserAvatar({
    super.key,
    required this.name,
    required this.imageUrl,
    this.size = 40,
  });

  final String name;
  final String imageUrl;
  final double size;

  @override
  Widget build(BuildContext context) {
    return CircleAvatar(
      radius: size / 2,
      backgroundImage: NetworkImage(imageUrl),
      child: Text(name[0]),
    );
  }
}
dart
// 父组件 —— 通过参数传递数据
class ParentWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return UserAvatar(
      name: '张三',
      imageUrl: 'https://example.com/avatar.jpg',
      size: 60,
    );
  }
}

传递多种类型的数据

构造函数参数可以传递任意类型的数据,包括对象、列表、控制器等:

dart
class ProductCard extends StatelessWidget {
  const ProductCard({
    super.key,
    required this.product,
    this.onTap,
    this.onFavorite,
  });

  final Product product;       // 自定义对象
  final VoidCallback? onTap;   // 回调也可以通过构造函数传递
  final ValueChanged<bool>? onFavorite;

  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: Text(product.name),
      subtitle: Text(${product.price}'),
      trailing: IconButton(
        icon: Icon(
          product.isFavorite ? Icons.favorite : Icons.favorite_border,
        ),
        onPressed: () => onFavorite?.call(!product.isFavorite),
      ),
      onTap: onTap,
    );
  }
}

父传子时更新子组件

当父组件数据变化时,子组件会自动重建以反映新数据:

dart
class ParentPage extends StatefulWidget {
  @override
  State<ParentPage> createState() => _ParentPageState();
}

class _ParentPageState extends State<ParentPage> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 父组件状态变化 → 自动重建子组件,传入新值
        CounterDisplay(count: _count),
        ElevatedButton(
          onPressed: () => setState(() => _count++),
          child: const Text('+1'),
        ),
      ],
    );
  }
}

// 子组件 —— 接收并展示数据
class CounterDisplay extends StatelessWidget {
  const CounterDisplay({super.key, required this.count});

  final int count;

  @override
  Widget build(BuildContext context) {
    return Text('当前计数: $count', style: const TextStyle(fontSize: 24));
  }
}

TIP

子组件中应使用 final 声明接收的参数,确保数据不可变。如果子组件需要在接收值的基础上维护内部状态,应在 initState() 中将传入值赋给内部变量。

子传父:回调函数

子组件通过回调函数将事件和数据传递给父组件。这是子组件向父组件通信的标准方式。

基本用法

dart
// 子组件 —— 定义回调参数
class SubmitButton extends StatelessWidget {
  const SubmitButton({
    super.key,
    required this.onSubmit,
    this.isLoading = false,
  });

  final VoidCallback onSubmit;           // 无参回调
  final bool isLoading;

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: isLoading ? null : onSubmit,
      child: isLoading
          ? const SizedBox(
              width: 16,
              height: 16,
              child: CircularProgressIndicator(strokeWidth: 2),
            )
          : const Text('提交'),
    );
  }
}
dart
// 父组件 —— 传入回调实现
class FormPage extends StatefulWidget {
  @override
  State<FormPage> createState() => _FormPageState();
}

class _FormPageState extends State<FormPage> {
  bool _isLoading = false;

  Future<void> _handleSubmit() async {
    setState(() => _isLoading = true);
    await Future.delayed(const Duration(seconds: 2)); // 模拟网络请求
    setState(() => _isLoading = false);
  }

  @override
  Widget build(BuildContext context) {
    return SubmitButton(
      onSubmit: _handleSubmit,
      isLoading: _isLoading,
    );
  }
}

传递数据的回调

回调可以携带数据,常用类型如下:

回调类型定义使用场景
VoidCallbackvoid Function()无数据的事件通知
ValueChanged<T>void Function(T value)传递单个值
ValueSetter<T>void Function(T value)同 ValueChanged
ValueGetter<T>T Function()从外部获取值
自定义void Function(String name, int age)传递多个值
dart
// 子组件 —— 通过回调传递数据
class SearchBar extends StatelessWidget {
  const SearchBar({
    super.key,
    this.onSearch,
    this.onChanged,
  });

  final ValueChanged<String>? onSearch;    // 搜索提交
  final ValueChanged<String>? onChanged;   // 实时输入变化

  @override
  Widget build(BuildContext context) {
    return TextField(
      decoration: const InputDecoration(
        hintText: '搜索...',
        prefixIcon: Icon(Icons.search),
      ),
      onChanged: onChanged,
      onSubmitted: onSearch,
    );
  }
}
dart
// 父组件 —— 接收子组件传来的数据
class SearchPage extends StatefulWidget {
  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage> {
  String _keyword = '';
  List<String> _results = [];

  void _handleSearch(String keyword) {
    setState(() {
      _keyword = keyword;
      _results = ['结果1: $keyword', '结果2: $keyword'];
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        SearchBar(
          onSearch: _handleSearch,
          onChanged: (value) => _keyword = value,
        ),
        Expanded(
          child: ListView.builder(
            itemCount: _results.length,
            itemBuilder: (_, index) => ListTile(title: Text(_results[index])),
          ),
        ),
      ],
    );
  }
}

完整示例:开关组件通知父组件

dart
import 'package:flutter/material.dart';

// 子组件:开关
class ToggleSwitch extends StatefulWidget {
  const ToggleSwitch({
    super.key,
    required this.label,
    this.initialValue = false,
    this.onChanged,
  });

  final String label;
  final bool initialValue;
  final ValueChanged<bool>? onChanged;  // 回调:通知父组件开关状态

  @override
  State<ToggleSwitch> createState() => _ToggleSwitchState();
}

class _ToggleSwitchState extends State<ToggleSwitch> {
  late bool _isOn;

  @override
  void initState() {
    super.initState();
    _isOn = widget.initialValue;
  }

  void _toggle() {
    setState(() => _isOn = !_isOn);
    widget.onChanged?.call(_isOn);  // 通知父组件
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Text(widget.label),
        const Spacer(),
        Switch(value: _isOn, onChanged: (_) => _toggle()),
      ],
    );
  }
}
dart
// 父组件:接收子组件通知
class SettingsPage extends StatefulWidget {
  @override
  State<SettingsPage> createState() => _SettingsPageState();
}

class _SettingsPageState extends State<SettingsPage> {
  bool _darkMode = false;
  bool _notifications = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('设置')),
      body: Column(
        children: [
          ToggleSwitch(
            label: '深色模式',
            initialValue: _darkMode,
            onChanged: (value) {
              setState(() => _darkMode = value);
              // 应用深色模式主题...
            },
          ),
          ToggleSwitch(
            label: '通知',
            initialValue: _notifications,
            onChanged: (value) {
              setState(() => _notifications = value);
            },
          ),
          Text('深色模式: ${_darkMode ? "开启" : "关闭"}'),
          Text('通知: ${_notifications ? "开启" : "关闭"}'),
        ],
      ),
    );
  }
}

状态提升:父子双向通信

当多个子组件需要共享同一个状态时,将状态提升到父组件管理,通过「参数下传 + 回调上报」实现双向通信。

模式说明

        父组件(持有状态)
       ↙              ↘
  参数下传            参数下传
     ↓                  ↓
  子组件A            子组件B
     ↓                  ↑
  回调上报            回调上报

完整示例:购物车数量选择器

dart
import 'package:flutter/material.dart';

// 父组件 —— 持有状态,协调两个子组件
class QuantitySelector extends StatefulWidget {
  const QuantitySelector({super.key});

  @override
  State<QuantitySelector> createState() => _QuantitySelectorState();
}

class _QuantitySelectorState extends State<QuantitySelector> {
  int _quantity = 1;  // 状态提升到父组件

  void _increment() => setState(() => _quantity++);
  void _decrement() {
    if (_quantity > 1) setState(() => _quantity--);
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        // 子组件A:减少按钮,通过回调上报
        DecrementButton(onPressed: _decrement),
        const SizedBox(width: 16),
        // 子组件B:数量展示,通过参数下传
        QuantityDisplay(quantity: _quantity),
        const SizedBox(width: 16),
        // 子组件C:增加按钮,通过回调上报
        IncrementButton(onPressed: _increment),
      ],
    );
  }
}

// 子组件:减少按钮
class DecrementButton extends StatelessWidget {
  const DecrementButton({super.key, required this.onPressed});
  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return IconButton(
      onPressed: onPressed,
      icon: const Icon(Icons.remove_circle_outline),
    );
  }
}

// 子组件:数量展示
class QuantityDisplay extends StatelessWidget {
  const QuantityDisplay({super.key, required this.quantity});
  final int quantity;

  @override
  Widget build(BuildContext context) {
    return Text(
      '$quantity',
      style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
    );
  }
}

// 子组件:增加按钮
class IncrementButton extends StatelessWidget {
  const IncrementButton({super.key, required this.onPressed});
  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return IconButton(
      onPressed: onPressed,
      icon: const Icon(Icons.add_circle_outline),
    );
  }
}

何时使用状态提升

当两个或多个子组件需要基于同一个数据进行交互时,就应该将状态提升到它们的最近公共父组件中管理。这是 Flutter 中最常用的组件通信模式。

跨层级通信:InheritedWidget

当数据需要跨越多层 Widget 传递时,构造函数参数需要层层透传(俗称 "prop drilling"),非常繁琐。InheritedWidget 可以让子树中的任意组件直接获取数据,无需中间层透传。

问题:层层透传

dart
// ❌ 不推荐:数据需要一层层传递
class GrandParent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Parent(userName: '张三');  // 传递给 Parent
  }
}

class Parent extends StatelessWidget {
  final String userName;
  const Parent({super.key, required this.userName});

  @override
  Widget build(BuildContext context) {
    return Child(userName: userName);  // 继续透传给 Child
  }
}

class Child extends StatelessWidget {
  final String userName;
  const Child({super.key, required this.userName});

  @override
  Widget build(BuildContext context) {
    return Text(userName);  // 最终使用
  }
}

解决:InheritedWidget

dart
// ✅ 推荐:子组件直接获取数据
class UserData extends InheritedWidget {
  const UserData({
    super.key,
    required this.userName,
    required super.child,
  });

  final String userName;

  static UserData of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<UserData>()!;
  }

  @override
  bool updateShouldNotify(UserData oldWidget) => userName != oldWidget.userName;
}

// 顶层提供数据
UserData(
  userName: '张三',
  child: Parent(),  // 不需要透传参数
)

// 任意深层子组件直接获取
class DeepChild extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final userName = UserData.of(context).userName;  // 直接获取
    return Text(userName);
  }
}

更多详情

InheritedWidget 的完整用法请参考 InheritedWidget 详解。实际项目中推荐使用 Provider,它是对 InheritedWidget 的优雅封装。

跨层级通信:Provider

Provider 是 Flutter 官方推荐的状态管理方案,基于 InheritedWidget 封装,使用更简洁。

基本用法

dart
import 'package:provider/provider.dart';

// 1. 定义数据模型
class Counter extends ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();  // 通知监听者
  }
}

// 2. 在顶层注入
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => Counter(),
      child: const MyApp(),
    ),
  );
}

// 3. 子组件中读取数据
class CounterDisplay extends StatelessWidget {
  const CounterDisplay({super.key});

  @override
  Widget build(BuildContext context) {
    final count = context.watch<Counter>().count;  // 监听变化
    return Text('计数: $count');
  }
}

// 4. 子组件中调用方法
class IncrementButton extends StatelessWidget {
  const IncrementButton({super.key});

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () => context.read<Counter>().increment(),  // 调用方法
      child: const Text('+1'),
    );
  }
}

watch vs read

  • context.watch<T>():监听变化,数据更新时自动重建
  • context.read<T>():只读取一次,不监听变化,适合调用方法

更多详情

Provider 的完整用法请参考 Provider 详解

轻量级通知:ValueNotifier

ValueNotifier 是 Flutter 内置的轻量级变更通知机制,适合简单的单一数据场景,无需引入第三方包。

基本用法

dart
// 创建 ValueNotifier
final counterNotifier = ValueNotifier<int>(0);

// 修改值 —— 自动通知监听者
counterNotifier.value++;  // 值变化时自动通知

// 读取值
print(counterNotifier.value);  // 1

配合 ValueListenableBuilder 使用

dart
class CounterPage extends StatelessWidget {
  CounterPage({super.key});

  final _counter = ValueNotifier<int>(0);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        // ValueListenableBuilder 只在 value 变化时重建局部 UI
        child: ValueListenableBuilder<int>(
          valueListenable: _counter,
          builder: (context, value, child) {
            return Text(
              '$value',
              style: const TextStyle(fontSize: 48),
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _counter.value++,
        child: const Icon(Icons.add),
      ),
    );
  }
}

跨组件通信

dart
// 父组件创建并传递 ValueNotifier
class ParentWidget extends StatelessWidget {
  ParentWidget({super.key});

  final _username = ValueNotifier<String>('张三');

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 子组件A:展示数据
        UsernameDisplay(usernameNotifier: _username),
        // 子组件B:修改数据
        UsernameEditor(usernameNotifier: _username),
      ],
    );
  }
}

// 子组件A:监听变化
class UsernameDisplay extends StatelessWidget {
  const UsernameDisplay({super.key, required this.usernameNotifier});
  final ValueNotifier<String> usernameNotifier;

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<String>(
      valueListenable: usernameNotifier,
      builder: (context, name, _) => Text('用户名: $name'),
    );
  }
}

// 子组件B:修改数据
class UsernameEditor extends StatelessWidget {
  const UsernameEditor({super.key, required this.usernameNotifier});
  final ValueNotifier<String> usernameNotifier;

  @override
  Widget build(BuildContext context) {
    return TextField(
      onChanged: (value) => usernameNotifier.value = value,
      decoration: const InputDecoration(hintText: '修改用户名'),
    );
  }
}

注意释放

ValueNotifier 用完后需要调用 dispose() 释放资源。如果在 StatefulWidget 中使用,请在 dispose() 生命周期中释放:

dart
@override
void dispose() {
  _counter.dispose();
  super.dispose();
}

跨组件访问 State:GlobalKey

GlobalKey 可以让你在 Widget 树的任意位置访问某个 Widget 的 State 对象,实现跨组件通信。

基本用法

dart
// 1. 创建 GlobalKey
final _formKey = GlobalKey<FormState>();

// 2. 关联到 Widget
Form(
  key: _formKey,
  child: Column(
    children: [
      TextFormField(validator: (value) => value?.isEmpty == true ? '请输入' : null),
      ElevatedButton(
        onPressed: () {
          // 3. 通过 GlobalKey 访问 State
          if (_formKey.currentState!.validate()) {
            // 表单验证通过
          }
        },
        child: const Text('提交'),
      ),
    ],
  ),
)

跨组件调用子组件方法

dart
class ParentPage extends StatelessWidget {
  ParentPage({super.key});

  final _childKey = GlobalKey<_ChildWidgetState>();

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ChildWidget(key: _childKey),
        ElevatedButton(
          onPressed: () {
            // 通过 GlobalKey 调用子组件的方法
            _childKey.currentState?.reset();
          },
          child: const Text('重置子组件'),
        ),
      ],
    );
  }
}

class ChildWidget extends StatefulWidget {
  const ChildWidget({super.key});

  @override
  State<ChildWidget> createState() => _ChildWidgetState();
}

class _ChildWidgetState extends State<ChildWidget> {
  int _count = 0;

  void reset() {
    setState(() => _count = 0);
  }

  @override
  Widget build(BuildContext context) {
    return Text('计数: $_count');
  }
}

谨慎使用 GlobalKey

GlobalKey 开销较大,应避免滥用。大多数场景下,优先使用回调函数或状态管理方案。GlobalKey 主要适用于表单验证、ScaffoldScaffoldMessenger 等框架内置场景。

兄弟组件通信

Flutter 中没有直接的兄弟组件通信机制,需要通过以下方式间接实现:

方式一:状态提升(推荐)

将共享状态提升到父组件,通过参数和回调协调两个子组件。参见上方 状态提升 章节。

方式二:Provider / InheritedWidget

兄弟组件通过共同的父级 Provider 或 InheritedWidget 共享数据:

dart
// 兄弟组件A:修改数据
class EditButton extends StatelessWidget {
  const EditButton({super.key});

  @override
  Widget build(BuildContext context) {
    return IconButton(
      icon: const Icon(Icons.edit),
      onPressed: () => context.read<EditMode>().toggle(),
    );
  }
}

// 兄弟组件B:监听数据
class ContentDisplay extends StatelessWidget {
  const ContentDisplay({super.key});

  @override
  Widget build(BuildContext context) {
    final isEditing = context.watch<EditMode>().isEditing;
    return isEditing ? const TextField() : const Text('展示内容');
  }
}

方式三:EventBus(不推荐)

EventBus 是一种发布/订阅模式的事件总线,可以实现完全解耦的跨组件通信:

dart
import 'package:event_bus/event_bus.dart';

// 创建全局 EventBus
final eventBus = EventBus();

// 定义事件
class UserLoggedInEvent {
  final String userName;
  UserLoggedInEvent(this.userName);
}

// 组件A:发送事件
eventBus.fire(UserLoggedInEvent('张三'));

// 组件B:监听事件
@override
void initState() {
  super.initState();
  _subscription = eventBus.on<UserLoggedInEvent>().listen((event) {
    print('用户登录: ${event.userName}');
  });
}

@override
void dispose() {
  _subscription.cancel();  // 务必取消订阅
  super.dispose();
}

EventBus 的缺点

  • 事件流难以追踪和调试
  • 容易忘记取消订阅导致内存泄漏
  • 破坏了组件的依赖关系,降低代码可维护性

实际项目中不推荐使用 EventBus,优先使用状态提升或 Provider。

方案选择指南

场景推荐方案
父组件向直接子组件传数据构造函数参数
子组件通知父组件事件回调函数
多个子组件共享同一状态状态提升(参数 + 回调)
深层子组件需要获取顶层数据InheritedWidget / Provider
简单的单一值跨组件响应ValueNotifier
复杂的跨组件状态管理Provider / Riverpod
访问子组件 State 的方法GlobalKey(仅特殊场景)

核心原则

  1. 优先使用构造函数参数和回调 —— 简单直接,依赖关系清晰
  2. 跨层级通信优先使用 Provider —— 官方推荐,生态完善
  3. 避免过度设计 —— 不要在简单场景引入复杂的状态管理方案
  4. 保持数据流方向清晰 —— 数据向下传递,事件向上汇报

下一步

基于 Flutter 官方文档整理