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(如 ThemeMediaQuery),当那个 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 方法。

相关推荐
GitLqr13 小时前
Flutter 实战:使用 local_auth 实现生物识别(指纹/Face ID)
安全·flutter·全栈
大龄秃头程序员15 小时前
【Flutter 性能踩坑小记】相册选个图卡了
flutter
ljt27249606611 天前
Flutter笔记--get_it&injectable
flutter
恋猫de小郭1 天前
Flutter iOS 的深度优化 PR,搞笑的是贡献者被 Gemini 评审折磨
android·前端·flutter
GitLqr2 天前
Flutter + Unity 混合开发:用 unity_kit 实现高效的双向通信
flutter·unity3d·全栈
程序员老刘2 天前
Flutter版本选择指南:3.47压哨发布,9月大限只剩一周 | 2026年8月
flutter·ai编程·客户端
xcyxiner2 天前
flutter 运行到模拟器上
android·前端·flutter
末代iOS程序员华仔2 天前
iOS 语聊直播/聊天APP4.3条例被拒解决
flutter·ios·swift
恋猫de小郭2 天前
Dart 3.13 的到底改了什么?为什么很重要?有什么坑?
android·前端·flutter