一、核心区别:浅对比 vs 深对比
关键概念
// 不可变对象:可以用引用对比(浅对比)
final a = 'Hello';
final b = 'Hello';
a == b // true(值对比)
identical(a, b) // 可能 true(引用对比,字符串池优化)
// 可变对象:必须用值对比(深对比)
final list1 = [1, 2, 3];
final list2 = [1, 2, 3];
list1 == list2 // true(值对比,需要遍历)
identical(list1, list2) // false(不同对象)
二、不可变 Widget 的对比优势
场景 1:const Widget 的极致优化
// 不可变 + const:编译时创建,全局共享
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
const Text('Hello'), // Widget A
const Text('Hello'), // Widget B
],
);
}
}
// 内存中的实际情况
const textA = Text('Hello'); // 地址:0x1000
const textB = Text('Hello'); // 地址:0x1000(同一个对象!)
print(identical(textA, textB)); // true
// Flutter 的对比
if (identical(oldWidget, newWidget)) {
// ⚡ 引用相同,直接跳过!O(1) 操作
return; // 不需要对比任何属性
}
性能对比
// ✅ const Widget:引用对比
identical(widget1, widget2) // 1 次指针比较,纳秒级
// ❌ 非 const Widget:需要对比属性
widget1.text == widget2.text &&
widget1.style == widget2.style &&
widget1.textAlign == widget2.textAlign &&
// ... 对比 N 个属性,微秒级
场景 2:不可变对象的安全假设
// 不可变 Widget:可以安全地缓存和比较
class ImmutableWidget extends StatelessWidget {
final String title; // final = 不可变
final int count;
const ImmutableWidget({required this.title, required this.count});
@override
bool operator ==(Object other) {
if (identical(this, other)) return true; // ⚡ 快速路径
return other is ImmutableWidget &&
title == other.title && // 只需对比值
count == other.count;
}
}
// Flutter 的对比逻辑
Widget oldWidget = ImmutableWidget(title: 'A', count: 1);
Widget newWidget = ImmutableWidget(title: 'A', count: 1);
// 第一步:引用对比(最快)
if (identical(oldWidget, newWidget)) {
return; // ⚡ 跳过
}
// 第二步:值对比(较快,因为不可变)
if (oldWidget == newWidget) {
// ✅ 值相同,跳过重建
// 因为不可变,可以确信内部状态也没变
}
三、可变对象的对比困境
问题:可变对象无法信任
// ❌ 假设 Widget 可变
class MutableWidget extends Widget {
String title; // 可变!
List<String> items; // 可变!
MutableWidget({required this.title, required this.items});
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
// 问题 1:即使引用相同,内容可能已经变了!
if (identical(this, other)) {
// ⚠️ 不能直接返回 true,因为 title 可能被修改了
// 必须检查所有字段
}
return other is MutableWidget &&
title == other.title && // 简单值对比
_deepEquals(items, other.items); // ❌ 需要深度对比!
}
// 深度对比:昂贵的操作
bool _deepEquals(List a, List b) {
if (a.length != b.length) return false;
for (int i = 0; i < a.length; i++) {
if (a[i] != b[i]) return false; // 遍历所有元素
}
return true;
}
}
// 使用场景
final widget = MutableWidget(title: 'A', items: ['1', '2', '3']);
// 问题:外部可能修改了内容
widget.title = 'B'; // 😱 修改了!
widget.items.add('4'); // 😱 修改了!
// Flutter 无法知道是否需要重建
// 必须每次都深度对比所有属性
性能灾难
// 可变 Widget 的对比成本
class MutableComplexWidget {
String title;
List<Item> items; // 假设有 1000 个元素
Map<String, dynamic> config; // 假设有 100 个键值对
CustomObject data; // 嵌套对象
@override
bool operator ==(Object other) {
return other is MutableComplexWidget &&
title == other.title && // O(1)
_compareList(items, other.items) && // O(n) = O(1000)
_compareMap(config, other.config) && // O(m) = O(100)
_compareObject(data, other.data); // O(?) 递归对比
}
// 总成本:O(1000 + 100 + ...) 每次重建都要执行!
}
// 不可变 Widget 的对比成本
class ImmutableComplexWidget {
final String title;
final List<Item> items;
final Map<String, dynamic> config;
final CustomObject data;
const ImmutableComplexWidget(...);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true; // ⚡ O(1) 快速路径
// 因为不可变,可以安全地对比引用
return other is ImmutableComplexWidget &&
title == other.title && // O(1)
identical(items, other.items) && // ⚡ O(1) 引用对比!
identical(config, other.config) && // ⚡ O(1)
identical(data, other.data); // ⚡ O(1)
}
// 总成本:O(1) 或 O(字段数量),远小于深度对比
}
四、"对比属性"的两种含义
1. 不可变 Widget:浅对比(引用对比)
class ImmutableWidget {
final String title;
final List<int> numbers; // 不可变引用
const ImmutableWidget({required this.title, required this.numbers});
@override
bool operator ==(Object other) {
return other is ImmutableWidget &&
title == other.title && // 字符串值对比(O(n),但 n 很小)
identical(numbers, other.numbers); // ⚡ 引用对比(O(1))
// 不需要对比 numbers 的每个元素!
}
}
// 使用
final list1 = [1, 2, 3];
final widget1 = ImmutableWidget(title: 'A', numbers: list1);
final widget2 = ImmutableWidget(title: 'A', numbers: list1); // 同一个 list
widget1 == widget2 // true
// 对比过程:
// 1. title == title ✓ (字符串对比)
// 2. identical(list1, list1) ✓ (引用对比,O(1))
2. 可变 Widget:深对比(值对比)
class MutableWidget {
String title;
List<int> numbers; // 可变引用
MutableWidget({required this.title, required this.numbers});
@override
bool operator ==(Object other) {
if (other is! MutableWidget) return false;
// ❌ 不能用引用对比,因为内容可能变了
// if (identical(numbers, other.numbers)) return true; // 不安全!
// 必须深度对比
if (numbers.length != other.numbers.length) return false;
for (int i = 0; i < numbers.length; i++) {
if (numbers[i] != other.numbers[i]) return false; // O(n)
}
return title == other.title;
}
}
// 使用
final list = [1, 2, 3];
final widget1 = MutableWidget(title: 'A', numbers: list);
final widget2 = MutableWidget(title: 'A', numbers: list); // 同一个 list
// 问题:即使是同一个 list,也不能信任
list.add(4); // 😱 list 被修改了!
// 必须每次都遍历对比
widget1 == widget2 // 需要遍历 list 的所有元素
五、实际案例对比
案例:列表 Widget
// ✅ 不可变设计
class ImmutableListWidget extends StatelessWidget {
final List<String> items; // final = 不可变引用
const ImmutableListWidget({required this.items});
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => Text(items[index]),
);
}
@override
bool operator ==(Object other) {
return other is ImmutableListWidget &&
identical(items, other.items); // ⚡ O(1) 引用对比
}
}
// 使用
final items = ['A', 'B', 'C'];
final widget1 = ImmutableListWidget(items: items);
final widget2 = ImmutableListWidget(items: items);
// Flutter 的对比
identical(widget1.items, widget2.items) // true,O(1)
// 不需要遍历 items 的每个元素!
// 如果要更新列表
final newItems = [...items, 'D']; // 创建新列表
final widget3 = ImmutableListWidget(items: newItems);
identical(widget1.items, widget3.items) // false,知道需要重建
// ❌ 可变设计
class MutableListWidget extends Widget {
List<String> items; // 可变
MutableListWidget({required this.items});
@override
bool operator ==(Object other) {
if (other is! MutableListWidget) return false;
// ❌ 不能用引用对比
// 必须深度对比每个元素
if (items.length != other.items.length) return false;
for (int i = 0; i < items.length; i++) {
if (items[i] != other.items[i]) return false; // O(n)
}
return true;
}
}
// 使用
final items = ['A', 'B', 'C'];
final widget1 = MutableListWidget(items: items);
// 问题:items 可能被修改
items.add('D'); // 😱 修改了原列表
// Flutter 无法知道是否需要重建
// 必须每次都遍历对比
六、Flutter 实际的优化策略
Flutter 的 Widget 对比实现
// Flutter 源码(简化版)
class Widget {
const Widget({this.key});
final Key? key;
// Flutter 默认不重写 ==
// 使用 Object 的默认实现:引用对比
@override
bool operator ==(Object other) => identical(this, other);
@override
int get hashCode => identityHashCode(this);
}
// Element 的对比逻辑
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType &&
oldWidget.key == newWidget.key;
// 注意:不对比 Widget 的其他字段!
}
关键理解:Flutter 默认只对比 runtimeType 和 key,不对比字段值!
// 实际行为
final widget1 = Text('Hello');
final widget2 = Text('Hello');
widget1 == widget2 // false(不同对象)
Widget.canUpdate(widget1, widget2) // true(类型和 key 相同)
// Flutter 会:
// 1. 调用 build() 重建
// 2. 但可能复用 RenderObject(底层优化)
const 的特殊优化
// const Widget:编译时创建,全局唯一
const widget1 = Text('Hello');
const widget2 = Text('Hello');
identical(widget1, widget2) // true(同一个对象!)
// Flutter 的对比
if (identical(oldWidget, newWidget)) {
// ⚡ 直接跳过,连 build() 都不调用
return;
}
七、总结
不可变 vs 可变的对比成本
| 对比类型 | 不可变 Widget | 可变 Widget |
|---|---|---|
| 引用对比 | ✅ O(1) 安全可靠 | ❌ 不安全(内容可能变) |
| 字段对比 | ✅ O(字段数) 浅对比 | ❌ O(所有元素) 深对比 |
| const 优化 | ✅ 编译时创建,全局共享 | ❌ 无法使用 const |
| 缓存策略 | ✅ 可以安全缓存 | ❌ 缓存不可靠 |
关键区别
// 不可变:可以信任引用
final list = [1, 2, 3];
final widget1 = ImmutableWidget(items: list);
final widget2 = ImmutableWidget(items: list);
identical(widget1.items, widget2.items) // true
// ✅ 可以确信内容相同,因为不可变
// 可变:不能信任引用
final list = [1, 2, 3];
final widget1 = MutableWidget(items: list);
list.add(4); // 😱 修改了
final widget2 = MutableWidget(items: list);
identical(widget1.items, widget2.items) // true
// ❌ 但内容已经不同了!必须深度对比
核心理解:
- 不可变 = 可以用引用对比 = O(1) = 快
- 可变 = 必须深度对比 = O(n) = 慢
- const = 全局共享 = identical = 最快
这就是为什么 Flutter 坚持 Widget 不可变的根本原因!