Flutter修改Container的decoration时如何避免子组件被重新创建
问题原因
- 核心原因来自
Container的内部实现逻辑:当decoration参数为非null值时,Container会自动在子组件外层包裹一层DecoratedBox实现装饰效果;当decoration为null时,不会添加这层包裹。 - 示例代码中
decoration参数在BoxDecoration实例和null之间来回切换,导致每次定时器触发setState更新时,Container输出的组件树结构发生变化:- decoration有值时结构:
Container -> DecoratedBox -> StatefulChild - decoration为null时结构:
Container -> StatefulChild
- decoration有值时结构:
- Flutter框架的Element树更新采用同层级diff规则:更新时只会对比同一位置新旧组件的
runtimeType和key,如果两个值不匹配,就会直接卸载旧位置的整个子节点树,重新创建新的子节点。切换decoration值时,Container的直接子节点类型在DecoratedBox和StatefulChild之间来回切换,旧子树会被完全销毁,新子树从零创建,因此StatefulChild的State会被反复初始化,init state called日志反复打印。 - 示例代码还存在一个不规范写法:
_StatefulChildState继承State时泛型参数传错,应该指定为当前关联的组件类型,否则会引发类型安全问题。
修复方案
- 保证组件树结构稳定,不要让
Container的decoration参数在null和非null之间切换。不需要显示边框时,不要把decoration设为null,而是永远传入BoxDecoration实例,仅把不需要的border属性设为null即可。 - 修复State的泛型参数错误。
修正后的build方法代码:
@override build(BuildContext context) { return Container( child: StatefulChild(), // 永远传入BoxDecoration实例,不返回null给decoration参数 decoration: BoxDecoration( border: (counter % 2 == 0) ? Border.all(color: const Color.fromARGB(255, 0, 0, 255)) : null ) ); }
修正后的StatefulChild状态类代码:
class _StatefulChildState extends State<StatefulChild>{ @override initState() { super.initState(); print("init state called"); } @override build(BuildContext context) { return Text("hi"); } }
修改后组件树层级固定为Container -> DecoratedBox -> StatefulChild,不会随更新发生结构变化,StatefulChild对应的Element会被正常复用,initState只会在组件首次挂载时执行一次。
内容的提问来源于stack exchange,提问作者Kyle Cooke
相关产品推荐
相关产品推荐

