☰
Flutter鸿蒙开发:无状态与有状态组件实战解析
2026/10/16 9:50:17 网站建设 项目流程

Flutter 和鸿蒙的组合最近成了圈子里聊得最多的话题之一。不管你是从 Android/iOS 转过来的,还是一直在纯 Flutter 生态里干活,面对鸿蒙设备上跑 Flutter 应用这个场景,都会先遇到一个绕不开的问题:组件到底怎么写?无状态组件和有状态组件的选择,看起来是二十多分钟就能讲完的基础概念,但真正落到鸿蒙应用的实际开发里,牵扯到的却是一整套关于界面组织、状态同步、生命周期管线和性能取舍的决策逻辑。

这篇文章不准备讲太多空对空的理论,而是把我在实际项目里反复折腾过的例子整理出来:无状态组件用在哪一步、有状态组件何时必须上场、两者之间怎么协作、在鸿蒙设备上调试时又会踩到哪些坑。全程用项目里真实存在过的代码片段来还原思路,希望能给准备入场的开发者一条能直接参考的路径。

1. 从鸿蒙适配说起:为什么先谈组件再谈框架

1.1 Flutter 在鸿蒙设备上的运行方式

要说组件之前,得先搞清楚 Flutter 的渲染管线在鸿蒙上是怎么搭起来的。Flutter 本身自带一套从 Dart 层到渲染层的完整框架,它不依赖系统自带的控件体系,而是通过自绘引擎把 UI 画在画布上。鸿蒙这边提供的原生控件是 ArkUI 那套声明式写法,和 Flutter 的组件模型其实是两套不同的东西。

但 Flutter 跑在鸿蒙上时,走的仍然是自绘路线。也就是说,你在 Dart 侧写的 Widget 树,会经过 Flutter 引擎的布局、绘制、合成,最终投射到鸿蒙的设备屏幕上。换句话说,组件的选择和写法,本质上还是在 Flutter 框架自己的规则体系里运作,只是运行环境换了。

这一点很重要,因为它意味着你在 Flutter 里积累的无状态和有状态组件的经验,依然可以直接迁移过来,差距主要体现在 Platform Channel 这一层的适配,以及部分和系统能力交互的插件可能需要在鸿蒙侧重新实现。

1.2 组件分类的底层逻辑

Flutter 把组件分成了StatelessWidget和StatefulWidget两大类。这个划分并不只是 API 形态上的不同,它对应的是两种完全不同的界面更新思维。

无状态组件意味着这个组件的表现完全由外部传入的数据决定,内部没有任何需要维护和变化的东西。你可以把它理解成一块印好文字的指示牌,牌子本身不会变,除非你把它换掉。有状态组件则是带了一个内部仓库的组件,仓库里存的数据变化时,组件自己会主动去刷新局部界面。

区分的心态应该是:界面会不会因为自身的原因发生变化?如果会,就需要 StatefulWidget;如果不会,就用 StatelessWidget。这听起来简单,但我在实际代码 review 里见过大量因为图省事而把所有组件都写成 StatefulWidget 的案例,后果就是状态散落各处、刷新范围失控、性能逐步劣化。

1.3 这套机制在鸿蒙上意味着什么

鸿蒙设备有 Phone、Pad、折叠屏、车机等不同形态,屏幕尺寸、分辨率、交互方式差异很大。组件化思维在这类多设备分发场景下是天然的优势——你可以把通用的 UI 单元设计成无状态组件,在不同设备上直接复用;把交互和业务逻辑隔离进有状态组件,保证核心逻辑的一致性。

举个例子,一个列表项的展示(标题、副标题、角标)完全适合做成无状态组件,因为它的所有内容都来自外部传入的对象;而列表项的展开/收起、收藏状态的切换,就必须交给有状态组件来处理。这样的拆法,在跨设备适配时,只需要调整布局参数,而不需要动业务逻辑。

2. 无状态组件:静态界面和派生数据的正确姿势

2.1 StatelessWidget 的工作原理

无状态组件的核心特点是build方法决定了它的一切。当父组件重建时,无状态组件也会重新执行build,生成一棵新的 Widget 子树,但 Flutter 框架会通过 Element 的比对来尽量复用已有的渲染节点。

看一个最简单的例子,在鸿蒙应用里展示一个用户信息卡片:

class UserInfoCard extends StatelessWidget { final String name; final String avatarUrl; final String role; const UserInfoCard({ super.key, required this.name, required this.avatarUrl, required this.role, }); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: const [ BoxShadow( color: Colors.black12, blurRadius: 8, offset: Offset(0, 2), ), ], ), child: Row( children: [ CircleAvatar( radius: 24, backgroundImage: NetworkImage(avatarUrl), ), const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(name, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), const SizedBox(height: 4), Text(role, style: const TextStyle(fontSize: 14, color: Colors.grey)), ], ), ], ), ); } }

这段代码里没有任何setState,也没有内部可变数据。name、avatarUrl、role全部从构造函数传入,组件只负责把它们渲染出来。即使它的父级频繁刷新,这个组件本身也没有任何状态要维护。

2.2 无状态组件在鸿蒙场景下的适用边界

从我实际开发的经验来看,以下场景优先考虑无状态组件。

第一类是纯展示型 UI 单元。比如文章列表的标题卡片、设备状态图标、订单详情里的商品条目,这些组件的所有信息都来自外部数据结构,内部不需要处理任何交互状态。

第二类是派生数据展示。把一个原始数据传入,在内部通过计算得到展示内容,比如根据用户等级计算出对应的徽章文案。

第三类是静态布局容器。比如页面骨架、固定的说明区、底部的版权信息区域。这类组件在生命周期内几乎不会发生任何视觉变化。

我在鸿蒙项目里就把页面的头部区域(包含大标题和副标题)设计成了无状态组件,传入不同的文案即可复用在多个页面上。

2.3 无状态组件的性能优势

很多开发者忽视了一个细节:无状态组件如果配合const构造,在某些情况下可以跳过重建。Flutter 在做 Widget 比对时,如果发现两个 Widget 实例是相同的 const 实例(canonicalized),就可以直接判定为相等,大幅减少重建的开销。

所以写无状态组件时,建议尽量把构造函数标记为const。来看一个对比:

// 非 const 版本,每次父组件重建都会创建新实例 class TitleText extends StatelessWidget { final String text; TitleText(this.text); @override Widget build(BuildContext context) { return Text(text); } } // const 版本,在参数不变时可以被框架直接复用 class TitleText extends StatelessWidget { final String text; const TitleText(this.text); @override Widget build(BuildContext context) { return Text(text); } }

这里的差别在普通的低频率刷新中肉眼可能看不出来,但在列表滚动、大量单元格重建的场景里,累计的性能差异非常可观。我在鸿蒙平板适配时,把一个商品列表的单元格改成 const 构造之后,滚动帧率有明显的改善。

2.4 无状态组件的重建时机

需要留意的是,无状态组件并不等于完全不重建。父组件setState时,子组件如果接收了新的参数,build方法一定会重新执行。唯一能让它跳过重建的办法是:const加参数不变,或者把组件包在适当的地方让 Flutter 的 element 树复用机制发挥作用。

在鸿蒙的实践里,我经常看到有人误以为无状态组件就永远不会刷新,结果数据变了界面没变,最后才发现是状态放在了错误的位置。记住一个法则:无状态组件的刷新由父级触发,本身没有触发的权利。

3. 有状态组件:动态交互的核心引擎

3.1 StatefulWidget 的组成结构

有状态组件的代码结构比无状态组件多一层。StatefulWidget本身是不可变的,它创建出的State对象才是真正保存状态的地方。这层拆分是很多新手理解上的分水岭。

通常的写法是StatefulWidget持有外部传入的配置参数,State持有内部可变的业务状态,通过setState来触发 UI 重建:

class CounterButton extends StatefulWidget { final String label; const CounterButton({super.key, required this.label}); @override State<CounterButton> createState() => _CounterButtonState(); } class _CounterButtonState extends State<CounterButton> { int _count = 0; void _increment() { setState(() { _count++; }); } @override Widget build(BuildContext context) { return ElevatedButton( onPressed: _increment, child: Text('${widget.label} $_count'), ); } }

注意这里_count定义在_CounterButtonState中,而widget.label仍然通过widget来访问。初始化时的widget.label是外部传入的参数,_count是组件内部的状态,两者更新机制完全不同。

3.2 State 生命周期完整解析

有状态组件的生命周期是我在鸿蒙项目里排查 bug 时最常参考的时间线。完整的调用顺序是:

initState() didChangeDependencies() build() didUpdateWidget() deactivate() dispose()

initState:State 对象创建后第一个被调用的方法,只执行一次。适合做初始数据加载、初始化控制器、注册监听器。但注意不要在这里调用BuildContext相关的异步操作。我在鸿蒙应用里就遇到过在 initState 里发起网络请求,结果 context 在请求返回前已经被销毁的情况,直接报了异常。

didChangeDependencies:在 initState 之后会被调用,之后当组件的 InheritedWidget 依赖发生变化时也会被调用。这个方法是处理可变依赖的标准位置,比如从上层主题组件读取当前配色。

didUpdateWidget:当父组件重新构建,并且给这个 StatefulWidget 传入了新的参数时被调用。在这里可以通过比较旧参数和新参数来决定是否要重新加载数据。

@override void didUpdateWidget(covariant DeviceStatusPanel oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.deviceId != widget.deviceId) { _loadDeviceDetail(widget.deviceId); } }

这段代码处理的就是“同一个组件位置被复用了,但传入的设备 ID 变了”的场景。比如在鸿蒙的多设备管理页面里,点击不同设备时详情区组件本身不变,但需要重新加载数据。不重写 didUpdateWidget 就会出现显示旧设备信息的 bug。

deactivate:组件从组件树中被移除时调用。通常用在需要清理一些临时状态的时候。

dispose:State 对象被永久销毁时调用。在这里必须释放所有资源:取消订阅、关闭控制器、释放动画。我在实际开发中总结了一个铁律:凡是 initState 里创建的可释放对象,必须有对应的 dispose 操作。

3.3 有状态组件的常见误区

误区一:凡是会变化就 setState。setState 不是万能的,它只重建当前 State 的 build 方法。如果你在一个深层嵌套的组件里调用 setState,而真正需要变化的内容在另外的子组件里,路径不匹配,问题会隐藏得很深。

误区二:把大状态全部塞进有状态组件。一个 State 负责几十个变量的情况在项目后期会导致灾难性的维护成本。每次 setState 都是全量刷新,哪怕只改了一个布尔值,整个 build 方法也会重新走一遍。为了避免这个问题,我的做法是按业务模块拆分 State,每个 State 只关注它自己的那部分状态。

误区三:State 里保存了从 widget 参数派生出来的数据。这类冗余状态是 bug 的温床,因为 widget 参数更新时,State 里存的那份旧值不会自动同步。正确的做法是让 State 只保存真正私有的状态,派生数据在 build 里实时计算。

3.4 有状态组件在鸿蒙应用中的典型场景

在鸿蒙的天气类应用里,一个典型的场景是“城市选择 + 天气数据展示”。页面顶部是城市切换区,中间是当前天气,下面是未来几天的列表。城市切换必然是有状态组件的活,因为切换之后整个页面的数据状态都要变。

另外一个典型场景是蓝牙扫描页面的状态机管理。扫描中、已发现设备、连接中、已连接,每种状态对应不同的 UI 展示。我把这些状态统一收敛到一个有状态组件里,通过 setState 切换状态,再配合 AnimatedSwitcher 做过渡动画,用户的体验会顺畅不少。

4. 实战示例拆解:一个鸿蒙待办事项应用的组件实践

4.1 整体结构设计

这个示例是我们在鸿蒙平板上做的一个团队任务管理 Demo,支撑了文章后面要讲的绝大多数组件规则。为了完整展示无状态和有状态组件如何协作,我挑其中三个核心 UI 单元来拆解。

整个页面结构大致是:顶部的任务进度概览区(无状态)、中间的待办列表项(无状态)、底部的添加任务入口(有状态)、以及包裹整个页面的任务管理容器(有状态)。

这样设计的目的是让状态尽量向上收敛,UI 单元尽可能保持无状态,方便复用和测试。

4.2 无状态组件示例:任务卡片

任务卡片是这个应用里最基础也最典型的无状态组件。它接收一个任务对象,渲染出任务标题、优先级标签和完成状态勾选区域:

class TaskCard extends StatelessWidget { final Task task; final VoidCallback? onToggle; const TaskCard({ super.key, required this.task, this.onToggle, }); @override Widget build(BuildContext context) { return Card( child: ListTile( leading: Checkbox( value: task.isCompleted, onChanged: (_) => onToggle?.call(), ), title: Text( task.title, style: TextStyle( decoration: task.isCompleted ? TextDecoration.lineThrough : null, ), ), subtitle: Text('优先级:${task.priority}'), trailing: Icon( task.priority == Priority.high ? Icons.flag : Icons.flag_outlined, ), ), ); } }

这里我把点击勾选的逻辑通过onToggle回调暴露出去,组件本身不维护任何状态。这样做的直接好处是:任务卡片可以在任何地方复用,无论是主列表、搜索结果还是归档列表,只要传入不同的Task对象和回调函数就行。单元测试时也不需要去构造复杂的组件上下文。

4.3 有状态组件示例:新建任务对话框

新建任务的对话框是有状态组件的典型场景。它需要维护用户当前输入的标题文本、选定的优先级、是否展开更多选项等内部状态:

class AddTaskDialog extends StatefulWidget { final Future<void> Function(Task task) onTaskCreate; const AddTaskDialog({super.key, required this.onTaskCreate}); @override State<AddTaskDialog> createState() => _AddTaskDialogState(); } class _AddTaskDialogState extends State<AddTaskDialog> { final TextEditingController _titleController = TextEditingController(); Priority _priority = Priority.medium; bool _isSubmitting = false; void _submit() async { final title = _titleController.text.trim(); if (title.isEmpty) return; setState(() { _isSubmitting = true; }); try { await widget.onTaskCreate(Task(title: title, priority: _priority)); if (mounted) { Navigator.of(context).pop(); } } finally { if (mounted) { setState(() { _isSubmitting = false; }); } } } @override void dispose() { _titleController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AlertDialog( title: const Text('新建任务'), content: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: _titleController, decoration: const InputDecoration(labelText: '任务标题'), ), const SizedBox(height: 16), SegmentedButton<Priority>( segments: const [ ButtonSegment(value: Priority.high, label: Text('高')), ButtonSegment(value: Priority.medium, label: Text('中')), ButtonSegment(value: Priority.low, label: Text('低')), ], selected: {_priority}, onSelectionChanged: (value) { setState(() { _priority = value.first; }); }, ), ], ), actions: [ TextButton( onPressed: _isSubmitting ? null : () => Navigator.of(context).pop(), child: const Text('取消'), ), ElevatedButton( onPressed: _isSubmitting ? null : _submit, child: _isSubmitting ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : const Text('创建'), ), ], ); } }

这个例子可以看到有状态组件的几个关键点:控制器需要手动释放,异步操作后需要检查mounted再操作界面,提交状态(_isSubmitting)可以防止重复点击。这些都是实际运行中容易遗漏的细节。

4.4 组件间协作模式

真正有意思的是无状态和有状态组件如何在一个页面里协作。在任务管理页面里,最外层的TaskBoardPage是一个有状态组件,它维护着任务列表数据,可用的操作包括新增任务、切换完成状态、删除任务。

TaskBoardPage的 build 方法里,把任务列表映射成一组TaskCard(无状态组件),并传入对应的回调:

class TaskBoardPage extends StatefulWidget { const TaskBoardPage({super.key}); @override State<TaskBoardPage> createState() => _TaskBoardPageState(); } class _TaskBoardPageState extends State<TaskBoardPage> { List<Task> _tasks = []; void _addTask(Task task) { setState(() { _tasks.add(task); }); } void _toggleTask(int index) { setState(() { _tasks[index] = _tasks[index].copyWith(isCompleted: !_tasks[index].isCompleted); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('团队任务板')), body: ListView.builder( itemCount: _tasks.length, itemBuilder: (context, index) { return TaskCard( task: _tasks[index], onToggle: () => _toggleTask(index), ); }, ), floatingActionButton: FloatingActionButton( child: const Icon(Icons.add), onPressed: () async { final dialogTask = await showDialog<Task>( context: context, builder: (context) => AddTaskDialog(onTaskCreate: _addTask), ); }, ), ); } }

状态全部集中在_TaskBoardPageState里,TaskCard只负责展示和透传回调。数据流是单向的:状态向上,事件向上,UI 向下。这套模式在鸿蒙项目的多页面协作中非常实用。

5. 状态提升与组件生命周期进阶

5.1 为什么需要状态提升

在编写鸿蒙应用的过程中,有一类 bug 特别容易反复出现:一个状态本来只在某一个小组件内部使用,后来发现另一个组件也需要读取这个状态,于是只能把状态暴力提升到父组件。这种“先放进去再说”的做法,容易导致状态管理混乱。

状态提升的原则是:如果多个组件需要共享同一个状态,就把这个状态放到它们的公共父级中,再通过构造参数向下分发。但要注意,提升过程中要控制状态的作用域,不要把状态过早地放到页面顶层,否则每个角落的组件一动,整个页面都要重建。

在实际开发中,我习惯先把状态放在最靠近使用者的层级;一旦发现共享需求,再按上述原则提升。这种渐进式重构比一开始就设计一个巨大的全局状态树要稳得多。

5.2 setState 的代价与优化空间

每次调用setState,当前组件的 build 方法都会重新执行,并同步重建其下所有子组件的 Widget 对象。如果子组件里包含较重的计算或大量的构造工作,刷新的代价会比较大。

在鸿蒙设备上,中低端机型的 UI 线程性能远不如开发用的模拟器。曾经有一次,我把一个包含大列表的页面强刷,每次 setState 都会重复构建整个列表的 Widget 树,在低端平板上直接出现明显掉帧。

优化的方向有这几个:

  • 尽量缩小 setState 的范围,把状态拆分到真正需要刷新的子组件里
  • 列表项使用 const 构造 + 稳定的对象引用,让 Flutter 可以复用 element
  • 对于高频更新的数据(比如进度条、实时读数),考虑抽成独立组件,避免连累整个页面

5.3 组件的通信方式

除了父子组件之间通过构造函数传参和回调函数通信之外,Flutter 还提供了几种跨层级通信的方式。

InheritedWidget适合从上层向下传递配置信息。比如应用的主题色、当前的语言环境、用户登录状态。子组件可以通过dependOnInheritedWidgetOfExactType来获取,当上游数据变化时,依赖它的组件会自动重建。

ChangeNotifier配合ListenableBuilder是更轻量的事件通知机制。可以把某个状态对象定义为ChangeNotifier,组件只监听自己关心的状态。在任务管理应用里,我把团队成员的在线状态做成了 ChangeNotifier,只有头像区域监听了变化。

5.4 状态管理工具的引入时机

很多项目在初期阶段并不需要引入重量级的状态管理库。我自己在给鸿蒙应用做模块划分时,遵循的原则是:核心应用先用原生的setState+ 状态提升;当页面之间开始共享复杂业务状态、跨页面状态同步变得频繁时,再考虑引入第三方的状态管理方案。

这样做的好处是避免过度设计。初学者尤其容易被各种状态管理框架吸引,但框架本身并不能解决状态划分不清晰的问题。先把无状态和有状态组件用扎实了,比盲目上架构更实际。

6. 实战避坑:组件选择与生命周期踩坑记录

6.1 无状态组件滥用导致的 UI 不同步

接手一个鸿蒙应用模块时,发现有个页面一直存在“界面刷新了但内容没变”的诡异问题。排查到最后,发现那个列表项的组件被写成了无状态组件,数据加载完成后没有触发任何重建,页面停留在了旧数据上。

这类问题的根源是状态安置错误。无状态组件不能自己触发界面更新,所以那些需要“数据回来后显示结果”的场景,必须依赖更高层的有状态组件来驱动刷新。记住这个判断标准:组件自身是否有一个“从无到有、从加载到完成”的过程?如果有,它就是有状态的。

6.2 State 资源释放遗漏

在鸿蒙应用里做地图相关的页面时,遇到过一个比较隐蔽的问题:地图控制器在dispose中没有销毁,导致页面反复进出后,地图资源没有被释放,内存不断上涨,最终在低内存机型上出现卡死。

排查过程花了不少时间,因为错误信息并不显眼,只能通过抓内存快照发现疑似泄漏的控制器对象。从那之后,我对所有包含Controller字段的 State 都会格外留意,无论大小,一律在dispose里处理干净。

6.3 生命周期回调与异步操作的竞态

这个坑非常典型:在有状态组件里发起网络请求,用户很快退出页面,请求返回后调用setState,直接触发“setState after dispose”的异常。在鸿蒙的高版本 Flutter 适配中,这类问题偶尔会被 Crash 兜底机制吞掉,但留下的隐患更大。

正确的写法是:

if (mounted) { setState(() { _loading = false; _data = result; }); }

在异步回调中,先检查mounted再操作 State。这个习惯值得刻进肌肉记忆。尤其是应用里的页面跳转很快,用户往往会在数据返回前就切走,竞态问题出现的概率远比你想象的高。

6.4 常见问题速查表

整理一下这次实战中比较高频的问题和解决路径,方便大家直接对照:

现象可能原因解决思路
界面不刷新但数据已更新无状态组件内部持有可变数据将状态提升到父组件,通过 setState 驱动重建
页面闪烁或布局跳动setState 范围过大,重建了不相干的组件缩小状态作用域,拆分独立子组件
列表滚动卡顿列表项组件未做 const 优化使用 const 构造,减少 Widget 重建
低概率崩溃:setState after dispose异步回调未检查 mounted所有异步回调中先判断 mounted
页面内存持续上涨State 持有的控制器/监听器未释放在 dispose 中清理所有资源
组件数据错乱复用了同一个组件实例但没处理 didUpdateWidget重写 didUpdateWidget,比较新旧参数差异

7. 性能调优:组件粒度的精细化控制

7.1 组件拆分粒度与刷新区域控制

组件拆得越细,setState 的影响范围就越小,性能表现通常越好。但拆得太碎又会带来代码维护上的负担,需要在两者之间找平衡。

我在鸿蒙项目里常用的拆分策略是:一个页面只保留 2 到 3 个主要的状态中心。每个状态中心负责管理自己区域内的业务状态。其他区域性组件尽量收敛为无状态组件,只有在某个局部有独立交互时才把它升级成有状态组件。

拿一个设备控制页面举例:顶部设备状态栏、中部功能开关列表、底部数据图表区,它们之间互不干扰。把功能开关列表的状态独立到一个子组件里,点击开关时只重建列表区域,图表区就不会跟着闪动。

7.2 列表与滚动场景下的组件优化

鸿蒙设备上滚动性能始终是重点优化对象。任务列表这类场景,如果不做任何优化,每个列表项在滚动时都会重新执行 build。优化手段可以按优先级排列:

  • 列表项尽量保持无状态,减少内部状态
  • 使用itemExtent固定列表项高度,让 ListView 跳过布局计算
  • 对图片资源使用缓存管理,避免滚动时频繁发生 IO
  • 避免在 build 方法中执行网络请求、数据库查询等耗时操作

我遇到过最夸张的一次,是列表项里嵌了一个StreamBuilder去监听数据库变化,导致每次滚动都要触发数据库查询。改成父级统一订阅、通过参数传递数据之后,滚动流畅度提升了一个档次。

7.3 const 构造与组件复用

这剂“免费的性能药”值得单独强调。写组件时,凡是构造参数全部确定且没有运行时变化的,都应尽量标记为const。这不仅是代码风格问题,还能让 Flutter 的 Widget 复用机制发挥真正的作用。

// 推荐 const Text('确认', style: TextStyle(fontSize: 16)); // 不推荐 Text('确认', style: TextStyle(fontSize: 16));

前者的编译期就可以确定是一个常量,框架在重建时会直接复用同一个实例;后者每次都会新建对象。在大量列表项的渲染中,两种写法的差异会被放大成肉眼可见的卡顿。

7.4 避免在 build 方法中做重活

这个规则几乎所有 Flutter 教程都会讲,但在实际代码里仍然频繁被违反。我自己的一个习惯是:把 build 当纯函数来看待,它只负责根据当前状态产出 UI,不应该有任何副作用。

在一次优化中,我把一个本来在 build 里计算列表分组结果的逻辑移到了数据加载完成后统一处理,build 方法变成纯映射,性能提升立竿见影。类似的逻辑还有集合过滤、字符串拼接、对象拷贝,这些都应该提前算好,而不是在 build 里反复执行。

8. 在鸿蒙生态里用好 Flutter 组件的几点体会

真正把 Flutter 组件用到鸿蒙应用里之后,我对“无状态和有状态”的关系有了更实际的感受。它们不是两堵墙,而是一条光谱。无状态组件负责稳定地展示内容,有状态组件负责灵活动地响应变化,合理的应用应该在两者之间平滑过渡,让每个组件都待在最合适的位置上。

在鸿蒙的多设备场景下,这种组件化的收益会被进一步放大。同一个任务卡片组件,在手机上可以紧凑排列,在平板上可以扩展成更大的展示区域,在有状态容器不变的前提下,无状态的 UI 单元可以灵活适应不同屏幕的约束。

最后再分享一个小组件设计的技巧:编写组件时,先把组件自身要实现的功能列出来,区分“需要内部记忆的”和“完全由外部决定的”。前者放到 State 里,后者留给构造参数。这个看似简单的动作,能让你在后续维护中节省至少一半的排查时间。以上,就是这次鸿蒙应用组件实战中的完整沉淀,希望对正在入坑 Flutter on HarmonyOS 的开发者有参考价值。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询