Flutter (十六) 组件通信

组件通信

基础通信:父子组件之间

  • 父传子(构造函数传参) :父组件在实例化子组件时,通过构造函数将数据作为属性传递给子组件。子组件通常使用 final 关键字接收这些属性,以保证数据的不可变性。
  • 子传父(回调函数) :父组件将一个回调函数(Callback)作为参数传递给子组件。当子组件内部发生特定事件(如按钮点击、表单提交)时,调用该函数并将数据回传给父组件,父组件再根据接收到的数据更新自身状态。

进阶通信:跨层级组件之间

当组件嵌套层级过深时,如果依然使用构造函数层层传参,会导致代码极其臃肿(即"prop drilling"问题)。此时需要借助跨层通信机制:

  • InheritedWidget: Flutter 内置的底层机制,允许数据在 Widget 树中高效地自上而下传递。
  • Notification: 一种自下而上的事件冒泡机制。子 Widget 可以分发(dispatch)一个 Notification,任何上层的 NotificationListener 都可以监听到这个通知并进行处理,无需显式传递回调函数

状态管理:任意组件之间通信

跨组件,跨页面之间的数据共享和通信。

  • Provider 官方推荐的状态管理库,本质上是对InheritedWidget的封装。
  • BLoC / Riverpod:适用于复杂业务逻辑的状态管理方案。BLoC 基于事件(Events)和状态(States)分离的原则;Riverpod 则是 Provider 的下一代演进,提供了编译时安全和更灵活的依赖注入
  • GetX: 一个轻量级的微型框架,集成了状态管理、路由管理和依赖注入,以语法简洁和高性能著称
  • EventBus: 基于"发布/订阅"模式的事件总线。发布者和订阅者之间无需任何父子关系,非常适合全局事件通信(如用户登录状态变更、全局消息通知等)

架构层间通信 MVVM

在大型应用中,还需要考虑 View、ViewModel、Repository 和 Service 等架构层之间的通信规范。通常遵循依赖注入原则,各层通过构造函数接收依赖的对象(例如 ViewModel 通过构造函数接收 Repository),从而保持各层职责清晰、低耦合。

其它

  • 原生平台通信,通常使用MethodChannl进行一步方法调用,实现Flutter与iOS/Android通信。
  • Isolate通信,Dart并发模型,不同的Ioslate之间通过SendPort和ReceivePort进行消息传递。
  • 网络通信,使用Dio等网络请求插件。

父传子和子传父

scala 复制代码
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main(List<String> args) {
  runApp(const MaterialApp(home: HomePage()));
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('CustomScrollView 全方位知识点')),
      body: Container(
        color: Colors.green,
        alignment: Alignment.center,
        child: Column(
          children: [
            Text("这是父组件"),
            ShowPage(message: "这是第一个无状态子组件"),
            ShowPage(message: "这是第二个无状态子组件"),
            ChildPage(message: "这是第一个有状态子组件"),
            ChildPage(message: "这是第二个有状态子组件"),
            CallBackFather(),
          ],
        ),
      ),
    );
  }
}

class ShowPage extends StatelessWidget {
  //定义final属性
  final String message;
  const ShowPage({Key? key, required this.message}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(child: Text(message));
  }
}

class ChildPage extends StatefulWidget {
  final String message;
  ChildPage({Key? key, required this.message}) : super(key: key);

  @override
  _ChildPageState createState() => _ChildPageState();
}

class _ChildPageState extends State<ChildPage> {
  @override
  Widget build(BuildContext context) {
    return Container(child: Text("${widget.message}"));
  }
}

class CallBackFather extends StatefulWidget {
  CallBackFather({Key? key}) : super(key: key);

  @override
  _CallBackFatherState createState() => _CallBackFatherState();
}

class _CallBackFatherState extends State<CallBackFather> {
  int _count = 0;
  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      height: 60,
      child: Row(
        mainAxisAlignment: .center,
        children: [
          Text("这是父组件 $_count"),
          CallBackChild(
            callback: () {
              setState(() {
                _count += 1;
              });
            },
          ),
        ],
      ),
    );
  }
}

class CallBackChild extends StatelessWidget {
  final Function() callback;
  const CallBackChild({Key? key, required this.callback}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: GestureDetector(
        child: Text("callback + 1",style: TextStyle(color: Colors.amber,backgroundColor: Colors.black),),
        onTap: () {
          callback();
        },
      ),
    );
  }
}

跨层级的通信

less 复制代码
没有 InheritedWidget 时参数要层层 drilling// 没有 InheritedWidget → 层层传参

PageA(userId: "123", userName: "张三")
  └─ PageB(userId: "123", userName: "张三")  ← 被迫接收
       └─ PageC(userId: "123", userName: "张三")  ← 被迫接收
            └─ PageD(userId: "123", userName: "张三")  ← 被迫接收
                 └─ PageE(userId: "123", userName: "张三")  ← 真正需要

// B/C/D 根本不需要 userId 和 userName
// 但为了传给 E,不得不声明这些参数
// Widget 嵌套越深越痛苦

需要定义一个组件继承InheritedWidget。然后这个组件的所有子孙组件都可以获取。

less 复制代码
import 'package:flutter/material.dart';

void main(List<String> args) {
  runApp(const MaterialApp(home: SelectionArea(child: HomePage())));
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('InheritedWidget 组件通信')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: const [
          _Section1_What(),
          _Section2_Problem(),
          _Section3_HowToUse(),
          _Section4_FlutterOwn(),
          _Section5_DeepUpdate(),
          _Section6_Limitations(),
          _Section7_Summary(),
        ],
      ),
    );
  }
}

// ────────────────────────────────────────────────────────────────
// ① InheritedWidget 是什么
// ────────────────────────────────────────────────────────────────
class _Section1_What extends StatelessWidget {
  const _Section1_What();

  @override
  Widget build(BuildContext context) {
    return _Card(
      title: '① InheritedWidget 是什么',
      subtitle: '跨层级组件通信的"数据总线"',
      children: const [
        Text(
          '// 问题:Widget 树层级深时,数据一层层传太麻烦\n'
          'A → B → C → D → E\n'
          'A 有数据要给 E,必须 B/C/D 都带着这个参数\n'
          '→ 叫"参数 drilling(参数钻探)"\n\n'
          '// InheritedWidget 解决什么?\n'
          '把数据存在树上层的 InheritedWidget 里\n'
          '下层任意 Widget 都可以通过 context 直接找到它\n'
          '不用中间层一层层传\n\n'
          '// Flutter 框架里大量用了 InheritedWidget:\n'
          'Theme.of(context)       ← ThemeData 存在 InheritedWidget\n'
          'MediaQuery.of(context)  ← MediaQueryData 存在 InheritedWidget\n'
          'Scaffold.of(context)    ← ScaffoldState 存在 InheritedWidget\n'
          'Navigator.of(context)   ← Navigator 存在 InheritedWidget',
          style: TextStyle(fontSize: 11, fontFamily: 'monospace'),
        ),
      ],
    );
  }
}

// ────────────────────────────────────────────────────────────────
// ② 没有 InheritedWidget 时的痛点
// ────────────────────────────────────────────────────────────────
class _Section2_Problem extends StatelessWidget {
  const _Section2_Problem();

  @override
  Widget build(BuildContext context) {
    return _Card(
      title: '② 痛点:一层层传参',
      subtitle: '没有 InheritedWidget 时参数要层层 drilling',
      children: const [
        Text(
          '// 没有 InheritedWidget → 层层传参\n\n'
          'PageA(userId: "123", userName: "张三")\n'
          '  └─ PageB(userId: "123", userName: "张三")  ← 被迫接收\n'
          '       └─ PageC(userId: "123", userName: "张三")  ← 被迫接收\n'
          '            └─ PageD(userId: "123", userName: "张三")  ← 被迫接收\n'
          '                 └─ PageE(userId: "123", userName: "张三")  ← 真正需要\n\n'
          '// B/C/D 根本不需要 userId 和 userName\n'
          '// 但为了传给 E,不得不声明这些参数\n'
          '// Widget 嵌套越深越痛苦\n\n'
          '// 有了 InheritedWidget → 直接取\n\n'
          'class PageE extends StatelessWidget {\n'
          '  @override\n'
          '  Widget build(BuildContext context) {\n'
          '    final data = UserData.of(context);  // 直接取!\n'
          '    return Text(data.userName);\n'
          '  }\n'
          '}',
          style: TextStyle(fontSize: 11, fontFamily: 'monospace'),
        ),
      ],
    );
  }
}

// ────────────────────────────────────────────────────────────────
// ③ 怎么用 ------ 完整示例
// ────────────────────────────────────────────────────────────────
class _Section3_HowToUse extends StatelessWidget {
  const _Section3_HowToUse();

  @override
  Widget build(BuildContext context) {
    return _Card(
      title: '③ 怎么用 ------ 完整三步',
      subtitle: '定义 → 挂载 → 取用',
      children: [
        // 实际演示
        const _Demo(),
        const SizedBox(height: 8),
        Container(
          padding: const EdgeInsets.all(8),
          color: Colors.amber.shade50,
          child: const Text(
            '// 步骤 1:定义 InheritedWidget\n\n'
            'class CounterInherited extends InheritedWidget {\n'
            '  final int count;\n'
            '  final VoidCallback increment;\n'
            '\n'
            '  const CounterInherited({\n'
            '    super.key,\n'
            '    required this.count,\n'
            '    required this.increment,\n'
            '    required super.child,\n'
            '  });\n'
            '\n'
            '  // 步骤 3 的配套:提供 of(context) 静态方法\n'
            '  static CounterInherited of(BuildContext context) {\n'
            '    return context.dependOnInheritedWidgetOfExactType<CounterInherited>();\n'
            '  }\n'
            '\n'
            '  // ✅ 必须重写!决定什么时候通知下游更新\n'
            '  @override\n'
            '  bool updateShouldNotify(CounterInherited oldWidget) {\n'
            '    return count != oldWidget.count;  // 数据变了才通知\n'
            '  }\n'
            '}\n\n'
            '// 步骤 2:在树上层挂载(通常放 MaterialApp 或页面根)\n'
            'class _DemoState extends State<_Demo> {\n'
            '  int _count = 0;\n'
            '\n'
            '  @override\n'
            '  Widget build(BuildContext context) {\n'
            '    return CounterInherited(\n'
            '      count: _count,\n'
            '      increment: () => setState(() => _count++),\n'
            '      child: const Column(\n'
            '        children: [_PageA(), _PageB()],\n'
            '      ),\n'
            '    );\n'
            '  }\n'
            '}\n\n'
            '// 步骤 3:任意子 Widget 通过 of(context) 取数据\n'
            'class _PageA extends StatelessWidget {\n'
            '  @override\n'
            '  Widget build(BuildContext context) {\n'
            '    final inherited = CounterInherited.of(context);\n'
            '    return Text("count: \\\${inherited.count}");\n'
            '  }\n'
            '}',
            style: TextStyle(fontSize: 10.5, fontFamily: 'monospace'),
          ),
        ),
      ],
    );
  }
}

// ───── 演示组件 ─────
class _Demo extends StatefulWidget {
  const _Demo();

  @override
  State<_Demo> createState() => _DemoState();
}

class _DemoState extends State<_Demo> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        color: Colors.blue.shade50,
        borderRadius: BorderRadius.circular(8),
      ),
      padding: const EdgeInsets.all(8),
      child: CounterInherited(
        count: _count,
        increment: () => setState(() => _count++),
        reset: () => setState(() => _count = 0),
        child: const Column(
          children: [
            _DemoHeader(),
            SizedBox(height: 6),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [_DemoButton(), _DemoButton()],
            ),
            SizedBox(height: 6),
            _DemoFooter(),
          ],
        ),
      ),
    );
  }
}

// ───── 定义 InheritedWidget ─────
class CounterInherited extends InheritedWidget {
  final int count;
  final VoidCallback increment;
  final VoidCallback reset;

  const CounterInherited({
    super.key,
    required this.count,
    required this.increment,
    required this.reset,
    required super.child,
  });

  static CounterInherited of(BuildContext context) {
    final result = context
        .dependOnInheritedWidgetOfExactType<CounterInherited>();
    assert(result != null, 'CounterInherited 未找到');
    return result!;
  }

  @override
  bool updateShouldNotify(CounterInherited oldWidget) {
    return count != oldWidget.count;
  }
}

// ───── 三个子组件,分别取数据 ─────
class _DemoHeader extends StatelessWidget {
  const _DemoHeader();

  @override
  Widget build(BuildContext context) {
    final inherited = CounterInherited.of(context);
    return Text(
      'Header: count = ${inherited.count}',
      style: const TextStyle(fontSize: 12, fontWeight: FontWeight.bold),
    );
  }
}

class _DemoButton extends StatelessWidget {
  const _DemoButton();

  @override
  Widget build(BuildContext context) {
    final inherited = CounterInherited.of(context);
    return ElevatedButton(
      onPressed: inherited.increment,
      child: const Text('+1', style: TextStyle(fontSize: 12)),
    );
  }
}

class _DemoFooter extends StatelessWidget {
  const _DemoFooter();

  @override
  Widget build(BuildContext context) {
    final inherited = CounterInherited.of(context);
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text(
          'Footer: ${inherited.count}',
          style: const TextStyle(fontSize: 11),
        ),
        const SizedBox(width: 8),
        TextButton(
          onPressed: inherited.reset,
          child: const Text('reset', style: TextStyle(fontSize: 11)),
        ),
      ],
    );
  }
}

// ────────────────────────────────────────────────────────────────
// ④ Flutter 自己怎么用 ------ Theme.of 源码分析
// ────────────────────────────────────────────────────────────────
class _Section4_FlutterOwn extends StatelessWidget {
  const _Section4_FlutterOwn();

  @override
  Widget build(BuildContext context) {
    return _Card(
      title: '④ Flutter 框架里的 InheritedWidget',
      subtitle: 'Theme / MediaQuery 都是它',
      children: const [
        Text(
          '// Theme.of(context) 源码简化:\n\n'
          'class Theme extends InheritedWidget {\n'
          '  final ThemeData data;\n'
          '\n'
          '  static ThemeData of(BuildContext context) {\n'
          '    final widget = context.dependOnInheritedWidgetOfExactType<Theme>();\n'
          '    return widget!.data;\n'
          '  }\n'
          '\n'
          '  @override\n'
          '  bool updateShouldNotify(Theme oldWidget) {\n'
          '    return data != oldWidget.data;\n'
          '  }\n'
          '}\n\n'
          '// MaterialApp 内部已经帮你挂载了 Theme\n'
          '// 所以你可以在任意页面直接 Theme.of(context)\n'
          '// 这就是 InheritedWidget 的威力\n\n'
          '// 同样的还有:\n'
          'MediaQuery.of(context)   → MediaQuery\n'
          'Navigator.of(context)    → Navigator\n'
          'Scaffold.of(context)     → ScaffoldMessenger\n'
          'Localizations.of(context) → Localizations',
          style: TextStyle(fontSize: 11, fontFamily: 'monospace'),
        ),
      ],
    );
  }
}

// ────────────────────────────────────────────────────────────────
// ⑤ updateShouldNotify ------ 什么时候重新构建子组件
// ────────────────────────────────────────────────────────────────
class _Section5_DeepUpdate extends StatelessWidget {
  const _Section5_DeepUpdate();

  @override
  Widget build(BuildContext context) {
    return _Card(
      title: '⑤ updateShouldNotify 深解',
      subtitle: '不是简单的==,要考虑子组件是否依赖这个数据',
      children: const [
        Text(
          '// updateShouldNotify 是 InheritedWidget 的灵魂\n'
          '// 它决定:当 InheritedWidget 数据变了,\n'
          '// 哪些子组件需要 rebuild',
          style: TextStyle(fontSize: 11),
        ),
        SizedBox(height: 6),
        Text(
          '三种写法对比:',
          style: TextStyle(fontWeight: FontWeight.bold, fontSize: 11),
        ),
        SizedBox(height: 4),
        Text(
          '// 写法 1:无脑返回 true(最简单但性能差)\n'
          '@override\n'
          'bool updateShouldNotify(covariant InheritedWidget old) => true;\n'
          '// 任何变化都通知所有下游\n\n'
          '// 写法 2:用 == 比较(推荐,足够用)\n'
          '@override\n'
          'bool updateShouldNotify(CounterInherited old) {\n'
          '  return count != old.count;  // 只在 count 变了才通知\n'
          '}\n\n'
          '// 写法 3:精细控制哪个属性变了通知谁(高级)\n'
          '// 场景:InheritedWidget 有 10 个字段,A 只关心 name,B 只关心 age\n'
          '// 这时可以拆分多个 InheritedWidget\n'
          '// 或者用 ValueNotifier + Consumer 模式(类似 Provider)',
          style: TextStyle(fontSize: 11, fontFamily: 'monospace'),
        ),
      ],
    );
  }
}

// ────────────────────────────────────────────────────────────────
// ⑥ 局限性 & 与 Provider 的关系
// ────────────────────────────────────────────────────────────────
class _Section6_Limitations extends StatelessWidget {
  const _Section6_Limitations();

  @override
  Widget build(BuildContext context) {
    return _Card(
      title: '⑥ 局限性 & 与 Provider 的关系',
      subtitle: '为什么有了 InheritedWidget 还要 Provider/Riverpod?',
      children: const [
        Text(
          'InheritedWidget 的问题:',
          style: TextStyle(
            fontWeight: FontWeight.bold,
            fontSize: 11,
            color: Colors.red,
          ),
        ),
        Text(
          '1. 必须手动改 state → setState → 更新 InheritedWidget\n'
          '   没有状态管理,只有数据传递\n\n'
          '2. updateShouldNotify 要自己写,容易写错\n\n'
          '3. 多个 InheritedWidget 嵌套太啰嗦\n\n'
          '4. 不支持"只监听某个属性"\n'
          '   context.dependOnInheritedWidgetOfExactType\n'
          '   是全量监听',
          style: TextStyle(fontSize: 11),
        ),
        SizedBox(height: 6),
        Text(
          'Provider / Riverpod 是 InheritedWidget 的封装升级:',
          style: TextStyle(
            fontWeight: FontWeight.bold,
            fontSize: 11,
            color: Colors.green,
          ),
        ),
        Text(
          'Provider<T>(create: (_) => MyModel(), child: ...)\n'
          '// 底层就是 InheritedWidget + ChangeNotifier\n'
          '// 帮你自动处理 updateShouldNotify\n'
          '// 帮你自动 setState\n\n'
          'Consumer<T>(builder: (_, model, __) {\n'
          '  return Text(model.name);  // 只在 model 变化时 rebuild\n'
          '})\n\n'
          '// 结论:\n'
          '// • 简单场景 → 直接写 InheritedWidget\n'
          '// • 复杂状态管理 → 用 Riverpod(当前推荐)\n'
          '// • 面试要答出:Provider 底层就是 InheritedWidget',
          style: TextStyle(fontSize: 11, fontFamily: 'monospace'),
        ),
      ],
    );
  }
}

// ────────────────────────────────────────────────────────────────
// ⑦ 总结
// ────────────────────────────────────────────────────────────────
class _Section7_Summary extends StatelessWidget {
  const _Section7_Summary();

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(12),
      decoration: BoxDecoration(
        color: Colors.brown.shade50,
        borderRadius: BorderRadius.circular(8),
      ),
      child: const Text(
        '📌 InheritedWidget 完整总结\n\n'
        '1. 核心作用:跨层级共享数据,避免参数 drilling\n\n'
        '2. 三步用法:\n'
        '   ① 继承 InheritedWidget,重写 updateShouldNotify\n'
        '   ② 在树上层挂载(通常根节点)\n'
        '   ③ 子组件通过 InheritedClass.of(context) 取用\n\n'
        '3. 两个获取方法:\n'
        '   dependOnInheritedWidgetOfExactType\n'
        '     → 注册依赖,数据变了自动 rebuild ✅\n'
        '   getInheritedWidgetOfExactType\n'
        '     → 只取值,不注册依赖,数据变了不 rebuild\n\n'
        '4. updateShouldNotify:\n'
        '   返回 true → 通知所有 dependOn 的子组件 rebuild\n'
        '   返回 false → 不通知\n'
        '   推荐用属性 == 比较\n\n'
        '5. 与 Provider 的关系:\n'
        '   Provider 底层 = InheritedWidget + ChangeNotifier\n'
        '   InheritedWidget 是数据传递\n'
        '   Provider 是完整的状态管理\n\n'
        '6. Flutter 框架里到处是 InheritedWidget:\n'
        '   Theme / MediaQuery / Navigator / Scaffold',
        style: TextStyle(fontSize: 12),
      ),
    );
  }
}

// ────────────────────────────────────────────────────────────────
class _Card extends StatelessWidget {
  final String title;
  final String subtitle;
  final List<Widget> children;
  const _Card({
    required this.title,
    this.subtitle = '',
    required this.children,
  });

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.only(bottom: 16),
      padding: const EdgeInsets.all(12),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(10),
        border: Border.all(color: Colors.grey.shade300),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withValues(alpha: 0.05),
            blurRadius: 6,
            offset: const Offset(0, 2),
          ),
        ],
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            title,
            style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
          ),
          if (subtitle.isNotEmpty) ...[
            const SizedBox(height: 2),
            Text(
              subtitle,
              style: TextStyle(
                fontSize: 11,
                color: Colors.grey.shade600,
                fontStyle: FontStyle.italic,
              ),
            ),
          ],
          const SizedBox(height: 10),
          ...children,
        ],
      ),
    );
  }
}
相关推荐
mONESY1 小时前
React 前端如何不傻等后端接口?
前端·javascript·后端
岁月留痕1681 小时前
6 Flutter 篇:Flutter 分层式架构设计
前端
乘风gg1 小时前
AI Coding:从单兵提效到多 Agent 的团队全链路协作模式
前端·ai编程·claude
万维易源1 小时前
免费药品信息查询:用API 读懂常用药
java·前端·数据库·药品信息·药品查询·药品查询api
勾勾圈圈蛋蛋1 小时前
Vue2 与 Vue3 响应式数据原理详解
前端
用户69371750013841 小时前
深夜炸场!DeepSeek 没发新模型,却重构了整个 Agent 生态
前端·后端·github
岁月留痕1681 小时前
16 实战项目一:实现暗黑模式
前端
岁月留痕1681 小时前
7 Flutter 篇:玩转组件(一)
前端
码农阿豪1 小时前
Ubuntu部署GodoOS:Docker搭建Web办公桌面并实现远程访问
前端·ubuntu·docker