Skip to content

组件通信

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

通信方式概览

方式方向适用场景复杂度
构造函数参数父 → 子传递展示数据
回调函数子 → 父子组件通知父组件事件
状态回调 + 状态提升子 ↔ 父父组件管理状态,子组件触发修改⭐⭐
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('提交'),
    );
  }
}

传递数据的回调

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

回调类型定义使用场景
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
import 'package:flutter/material.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),
        border: OutlineInputBorder(),
      ),
      onChanged: onChanged,
      onSubmitted: onSearch,
    );
  }
}

// 父组件 —— 使用搜索组件
class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage> {
  String _query = '';
  String _submitted = '';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('搜索')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            SearchBar(
              // ─── ★ 回调上报数据到父组件 ──────────────
              onChanged: (value) => setState(() => _query = value),
              // ─── ☆ 回调上报数据到父组件 ──────────────
              onSearch: (value) => setState(() => _submitted = value),
            ),
            const SizedBox(height: 24),
            Text('实时输入: $_query'),
            Text('提交搜索: $_submitted'),
          ],
        ),
      ),
    );
  }
}

状态提升:父子双向通信

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

模式说明

        父组件(持有状态)
       ↙              ↘
  参数下传            参数下传
     ↓                  ↓
  子组件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: [
        DecrementButton(onPressed: _decrement),
        const SizedBox(width: 16),
        QuantityDisplay(quantity: _quantity),
        const SizedBox(width: 16),
        IncrementButton(onPressed: _increment),
      ],
    );
  }
}

// 子组件:展示
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 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 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
  }
}

解决: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 的完整实现请参考 状态管理 章节。实际项目中推荐使用 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>():只读取一次,不监听变化,适合调用方法

轻量级通知:ValueNotifier

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

配合 ValueListenableBuilder 使用

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

  // ─── ★ ValueNotifier 轻量级通知 ──────────────
  final _counter = ValueNotifier<int>(0);
  // ─── ☆ ValueNotifier 轻量级通知 ──────────────

  @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),
      ),
    );
  }
}

注意释放

ValueNotifier 用完后需要调用 dispose() 释放资源。如果在 StatefulWidget 中使用,请在 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('提交'),
      ),
    ],
  ),
)

谨慎使用 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 是一种发布/订阅模式的事件总线,可以实现完全解耦的跨组件通信:

EventBus 的缺点

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

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

方案选择指南

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

核心原则

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

下一步

基于 Flutter 官方文档整理