Flutter三棵树核心详解之Widget树完全解析(二)

1. 引言

上一篇博客介绍了Widget,这篇文章主要介绍下StatelessWidget的相关知识点以及优化方案。

2. Widget 的两大分类

Flutter中的Widget 主要分为两大类:StatelessWidget(无状态组件) 和 StatefulWidget(有状态组件)。这是从组件是否持有可变状态的角度进行的划分。

这篇文章主要介绍下StatelessWidget的相关知识。

3.StatelessWidget

StatelessWidget是无状态组件。

1.定义

Dart 复制代码
abstract class StatelessWidget extends Widget {
  const StatelessWidget({super.key});

  @override
  StatelessElement createElement() => StatelessElement(this);

  @protected
  Widget build(BuildContext context);
}

从定义中我们可以看到,StatelessWidget的方法很简单,重写了父类的key以及createElement和build方法。

StatelessWidget------纯函数式 UI:UI = f(配置 + BuildContext)。

2.工作原理

build() 通过组合一组更具体的 Widget 来描述 UI,递归展开,直到全部由 RenderObjectWidget 组成(它们直接创建 RenderObject,是递归终止点)。

StatelessWidget(抽象描述)

→ build() 返回更具体的 Widget

→ ...(递归)

→ RenderObjectWidget → createRenderObject() → RenderObject

3.使用场景

当UI只依赖自身配置​ + BuildContext​。

4.build的三种调用时机

1.首次插入树

以下面的代码为例:

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

void main() => runApp(const MaterialApp(home: FirstInsertDemo()));

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

  @override
  State<FirstInsertDemo> createState() => _FirstInsertDemoState();
}

class _FirstInsertDemoState extends State<FirstInsertDemo> {
  bool _show = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            ElevatedButton(
              onPressed: () => setState(() => _show = true),
              child: const Text('插入 Widget'),
            ),
            const SizedBox(height: 20),
            if (_show) const MyCounter(label: '首次插入'),
          ],
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    print('[$label] build() 被调用'); // ← 观察控制台
    return Text(label, style: const TextStyle(fontSize: 24));
  }
}

操作 & 输出:

  • 启动 App → 控制台没有​ build() 输出(MyCounter 还没在树里)

  • 点按钮 → 控制台打印 [首次插入] build() 被调用​ ← 这就是时机 ①

2.父类Widget配置变化

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

void main() => runApp(const MaterialApp(home: ParentChangeDemo()));

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

  @override
  State<ParentChangeDemo> createState() => _ParentChangeDemoState();
}

class _ParentChangeDemoState extends State<ParentChangeDemo> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            // 父级每次 setState 都传一个新的 count 值
            MyCounter(label: '计数: $_count'), // ← 配置变了
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () => setState(() => _count++),
              child: const Text('父级 setState'),
            ),
          ],
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    print('[MyCounter] build() 被调用, label="$label"'); // ← 观察控制台
    return Text(label, style: const TextStyle(fontSize: 24));
  }
}

操作 & 输出:

  • 每次点按钮 → _count++ → 父级 setState → 重建
  • 控制台每次都打印:MyCounter build() 被调用, label="计数: N"
  • 每次 label 值不同 → 新 Widget 配置 → build() 被调用 ← 这就是时机 ②

💡 如果改成 const MyCounter(label: '固定文字'),点按钮后 build() 不会再被调用 (因为 const 短路了 diff)。

3.依赖的InheritedWidget变化

Widget 通过 BuildContext 读取了某个 InheritedWidget(如 Theme、MediaQuery),当那个 InheritedWidget 更新时 → 所有依赖它的 Widget 被标记为 dirty → build() 被调用

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

void main() => runApp(const MaterialApp(home: InheritedChangeDemo()));

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

  @override
  State<InheritedChangeDemo> createState() => _InheritedChangeDemoState();
}

class _InheritedChangeDemoState extends State<InheritedChangeDemo> {
  Color _color = Colors.blue;

  @override
  Widget build(BuildContext context) {
    // 用 Theme 作为 InheritedWidget 的载体
    return Theme(
      data: ThemeData(primaryColor: _color),
      child: Scaffold(
        body: const Center(
          child: ColorReader(), // ← 这个 Widget 依赖 Theme
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            setState(() {
              _color = _color == Colors.blue ? Colors.red : Colors.blue;
            });
          },
          child: const Icon(Icons.color_lens),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    // 读取 InheritedWidget(Theme)
    final color = Theme.of(context).primaryColor;
    print('[ColorReader] build() 被调用, color=$color'); // ← 观察控制台
    return Container(
      width: 100,
      height: 100,
      color: color,
      child: const Center(child: Text('看我变色', style: TextStyle(color: Colors.white))),
    );
  }
}

操作&输出:

  • 启动 App → 控制台打印 [ColorReader] build() 被调用, color=MaterialColor(...)
  • 点 FAB 切换颜色 → Theme 数据变了 → ColorReader 依赖了 Theme.of(context)
  • 控制台再次打印:ColorReader build() 被调用, color=MaterialColor(...) ← 这就是时机 ③
  • 每次切颜色都触发一次

5.优化技巧

1.减少build递归创建的节点数

下面有一个过渡嵌套的例子:

Dart 复制代码
// ❌ 4 层嵌套,build 时创建 4 个 Element + RenderObject
Padding(
  padding: EdgeInsets.all(8),
  child: SizedBox(
    width: 100,
    child: Row(
      children: [Expanded(child: Text('hello'))],
    ),
  ),
);

优化后:

Dart 复制代码
// ✅ 1 个 Align 搞定定位,节点数从 4 降到 1
Align(
  alignment: Alignment.center,
  child: SizedBox(width: 100, child: Text('hello')),
);

同理:

  • 多个 Container + Decoration 叠层 → 换成 一个 CustomPaint

  • 复杂的 Row/Column/Padding/SizedBox 排列 → 换成 CustomSingleChildLayout

为什么?​ 每个 Widget 都会变成 Element + 可能创建 RenderObject,嵌套越深,rebuild 时遍历的节点越多。

2.使用const构造函数

Dart 复制代码
// ✅ 用户这样用你的 Widget:
const MyButton(); // Widget 层零分配,diff 时直接跳过

// 前提是你提供了 const 构造函数:
class MyButton extends StatelessWidget {
  const MyButton({super.key}); // ← const 构造函数
  @override
  Widget build(BuildContext context) => Text('Click');
}

Widget 实例在编译期就确定了,整个 App 只有一个实例。

canUpdate 时:新旧Widget是同一个const对象 → identical()为true → 直接跳过子树 diff。

3.Stateless→Stateful 来缓存子树

Dart 复制代码
// ❌ Stateless:每次父级重建,expensiveChild 都被重新 build
class MyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        header,
        expensiveChild, // ← 每次都重建
      ],
    );
  }
}

// ✅ Stateful:用 cached widget 避免重建
class _MyPageState extends State<MyPage> {
  late final expensiveChild = ExpensiveWidget(); // 只创建一次

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [header, expensiveChild], // expensiveChild 不变
    );
  }
}

4.把InheritedWidget依赖推到叶子节点

Dart 复制代码
// ❌ Theme 变化 → 整个 MyWidget 重建
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context); // ← 依赖 Theme
    return Container(
      color: theme.primaryColor,
      child: Text('Hello'), // ← Text 本身不依赖 Theme,但也被连带重建
    );
  }
}

// ✅ 把依赖 Theme 的部分抽成叶子 Widget
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: ColorfulText(), // ← 只有这个叶子依赖 Theme
    );
  }
}

class ColorfulText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context); // ← Theme 变化只重建这个叶子
    return Text('Hello', style: TextStyle(color: theme.primaryColor));
  }
}

Theme变化时,只有ColorfulText 重建,Container 和上面的子树全部跳过。

5.Widget优于helper方法

Dart 复制代码
// ❌ Helper 方法
Widget buildHeader() {
  return Padding(padding: EdgeInsets.all(8), child: Text('Header'));
}

// 父 Widget 调 setState() → buildHeader() 返回的新 Widget 没有 key
// → Flutter 无法确认"这是同一个逻辑组件"
// → 整棵子树被销毁重建
Dart 复制代码
// ✅ Widget 类
class Header extends StatelessWidget {
  const Header({super.key});
  @override
  Widget build(BuildContext context) =>
      Padding(padding: EdgeInsets.all(8), child: Text('Header'));
}

// 父 Widget 调 setState() → Header 是 const → canUpdate 直接跳过
// 即使不是 const,有 key 也能精确匹配,最小化重建

4.StatelessWidget知识点总结

StatelessWidget 是 Flutter 声明式 UI 的基本单元:它自己不画、不改、不存状态,只负责把"抽象描述"递归拆成"具体 RenderObject"------想让它快,就 const、少嵌套、Widget 类优于 helper 方法。

相关推荐
2601_9495758620 小时前
JSON5 跨端三方库仓颉适配全流程
前端·javascript·flutter
法的空间20 小时前
AI 编程时代,为什么 Dart 分析会拖慢大型项目的开发
android·flutter·ios
恋猫de小郭1 天前
Flutter 紧急发布 3.47.8 安全升级版本,建议都升级
android·前端·flutter
好的佩奇2 天前
Dart 之模式
android·flutter·dart
淡淡的香烟2 天前
Flutter实现多语言切换
android·flutter·1024程序员节
事圆则缓2 天前
从零搭建一套 Flutter 架构:先跑通数据流
flutter
程序员老刘3 天前
Flutter版本选择指南:3.44稳定了11周正式转正 | 2026年10月
flutter·ai编程·客户端
chicken_h3 天前
【无标题】
flutter·打包
事圆则缓3 天前
Flutter 底层渲染引擎怎么变了:从 Skia 到 Impeller,以及迁移时的坑
flutter
码林鼠4 天前
flutter状态管理入门教程-Riverpod
开发语言·javascript·flutter