一、核心區(qū)別:淺對(duì)比 vs 深對(duì)比
關(guān)鍵概念
// 不可變對(duì)象:可以用引用對(duì)比(淺對(duì)比)
final a = 'Hello';
final b = 'Hello';
a == b // true(值對(duì)比)
identical(a, b) // 可能 true(引用對(duì)比,字符串池優(yōu)化)
// 可變對(duì)象:必須用值對(duì)比(深對(duì)比)
final list1 = [1, 2, 3];
final list2 = [1, 2, 3];
list1 == list2 // true(值對(duì)比,需要遍歷)
identical(list1, list2) // false(不同對(duì)象)
二、不可變 Widget 的對(duì)比優(yōu)勢(shì)
場(chǎng)景 1:const Widget 的極致優(yōu)化
// 不可變 + const:編譯時(shí)創(chuàng)建,全局共享
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
const Text('Hello'), // Widget A
const Text('Hello'), // Widget B
],
);
}
}
// 內(nèi)存中的實(shí)際情況
const textA = Text('Hello'); // 地址:0x1000
const textB = Text('Hello'); // 地址:0x1000(同一個(gè)對(duì)象?。?
print(identical(textA, textB)); // true
// Flutter 的對(duì)比
if (identical(oldWidget, newWidget)) {
// ? 引用相同,直接跳過!O(1) 操作
return; // 不需要對(duì)比任何屬性
}
性能對(duì)比
// ? const Widget:引用對(duì)比
identical(widget1, widget2) // 1 次指針比較,納秒級(jí)
// ? 非 const Widget:需要對(duì)比屬性
widget1.text == widget2.text &&
widget1.style == widget2.style &&
widget1.textAlign == widget2.textAlign &&
// ... 對(duì)比 N 個(gè)屬性,微秒級(jí)
場(chǎng)景 2:不可變對(duì)象的安全假設(shè)
// 不可變 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 && // 只需對(duì)比值
count == other.count;
}
}
// Flutter 的對(duì)比邏輯
Widget oldWidget = ImmutableWidget(title: 'A', count: 1);
Widget newWidget = ImmutableWidget(title: 'A', count: 1);
// 第一步:引用對(duì)比(最快)
if (identical(oldWidget, newWidget)) {
return; // ? 跳過
}
// 第二步:值對(duì)比(較快,因?yàn)椴豢勺儯?if (oldWidget == newWidget) {
// ? 值相同,跳過重建
// 因?yàn)椴豢勺?,可以確信內(nèi)部狀態(tài)也沒變
}
三、可變對(duì)象的對(duì)比困境
問題:可變對(duì)象無(wú)法信任
// ? 假設(shè) 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:即使引用相同,內(nèi)容可能已經(jīng)變了!
if (identical(this, other)) {
// ?? 不能直接返回 true,因?yàn)?title 可能被修改了
// 必須檢查所有字段
}
return other is MutableWidget &&
title == other.title && // 簡(jiǎn)單值對(duì)比
_deepEquals(items, other.items); // ? 需要深度對(duì)比!
}
// 深度對(duì)比:昂貴的操作
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;
}
}
// 使用場(chǎng)景
final widget = MutableWidget(title: 'A', items: ['1', '2', '3']);
// 問題:外部可能修改了內(nèi)容
widget.title = 'B'; // ?? 修改了!
widget.items.add('4'); // ?? 修改了!
// Flutter 無(wú)法知道是否需要重建
// 必須每次都深度對(duì)比所有屬性
性能災(zāi)難
// 可變 Widget 的對(duì)比成本
class MutableComplexWidget {
String title;
List<Item> items; // 假設(shè)有 1000 個(gè)元素
Map<String, dynamic> config; // 假設(shè)有 100 個(gè)鍵值對(duì)
CustomObject data; // 嵌套對(duì)象
@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(?) 遞歸對(duì)比
}
// 總成本:O(1000 + 100 + ...) 每次重建都要執(zhí)行!
}
// 不可變 Widget 的對(duì)比成本
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) 快速路徑
// 因?yàn)椴豢勺?,可以安全地?duì)比引用
return other is ImmutableComplexWidget &&
title == other.title && // O(1)
identical(items, other.items) && // ? O(1) 引用對(duì)比!
identical(config, other.config) && // ? O(1)
identical(data, other.data); // ? O(1)
}
// 總成本:O(1) 或 O(字段數(shù)量),遠(yuǎn)小于深度對(duì)比
}
四、"對(duì)比屬性"的兩種含義
1. 不可變 Widget:淺對(duì)比(引用對(duì)比)
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 && // 字符串值對(duì)比(O(n),但 n 很小)
identical(numbers, other.numbers); // ? 引用對(duì)比(O(1))
// 不需要對(duì)比 numbers 的每個(gè)元素!
}
}
// 使用
final list1 = [1, 2, 3];
final widget1 = ImmutableWidget(title: 'A', numbers: list1);
final widget2 = ImmutableWidget(title: 'A', numbers: list1); // 同一個(gè) list
widget1 == widget2 // true
// 對(duì)比過程:
// 1. title == title ? (字符串對(duì)比)
// 2. identical(list1, list1) ? (引用對(duì)比,O(1))
2. 可變 Widget:深對(duì)比(值對(duì)比)
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;
// ? 不能用引用對(duì)比,因?yàn)閮?nèi)容可能變了
// if (identical(numbers, other.numbers)) return true; // 不安全!
// 必須深度對(duì)比
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); // 同一個(gè) list
// 問題:即使是同一個(gè) list,也不能信任
list.add(4); // ?? list 被修改了!
// 必須每次都遍歷對(duì)比
widget1 == widget2 // 需要遍歷 list 的所有元素
五、實(shí)際案例對(duì)比
案例:列表 Widget
// ? 不可變?cè)O(shè)計(jì)
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) 引用對(duì)比
}
}
// 使用
final items = ['A', 'B', 'C'];
final widget1 = ImmutableListWidget(items: items);
final widget2 = ImmutableListWidget(items: items);
// Flutter 的對(duì)比
identical(widget1.items, widget2.items) // true,O(1)
// 不需要遍歷 items 的每個(gè)元素!
// 如果要更新列表
final newItems = [...items, 'D']; // 創(chuàng)建新列表
final widget3 = ImmutableListWidget(items: newItems);
identical(widget1.items, widget3.items) // false,知道需要重建
// ? 可變?cè)O(shè)計(jì)
class MutableListWidget extends Widget {
List<String> items; // 可變
MutableListWidget({required this.items});
@override
bool operator ==(Object other) {
if (other is! MutableListWidget) return false;
// ? 不能用引用對(duì)比
// 必須深度對(duì)比每個(gè)元素
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 無(wú)法知道是否需要重建
// 必須每次都遍歷對(duì)比
六、Flutter 實(shí)際的優(yōu)化策略
Flutter 的 Widget 對(duì)比實(shí)現(xiàn)
// Flutter 源碼(簡(jiǎn)化版)
class Widget {
const Widget({this.key});
final Key? key;
// Flutter 默認(rèn)不重寫 ==
// 使用 Object 的默認(rèn)實(shí)現(xiàn):引用對(duì)比
@override
bool operator ==(Object other) => identical(this, other);
@override
int get hashCode => identityHashCode(this);
}
// Element 的對(duì)比邏輯
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType &&
oldWidget.key == newWidget.key;
// 注意:不對(duì)比 Widget 的其他字段!
}
關(guān)鍵理解:Flutter 默認(rèn)只對(duì)比 runtimeType 和 key,不對(duì)比字段值!
// 實(shí)際行為
final widget1 = Text('Hello');
final widget2 = Text('Hello');
widget1 == widget2 // false(不同對(duì)象)
Widget.canUpdate(widget1, widget2) // true(類型和 key 相同)
// Flutter 會(huì):
// 1. 調(diào)用 build() 重建
// 2. 但可能復(fù)用 RenderObject(底層優(yōu)化)
const 的特殊優(yōu)化
// const Widget:編譯時(shí)創(chuàng)建,全局唯一
const widget1 = Text('Hello');
const widget2 = Text('Hello');
identical(widget1, widget2) // true(同一個(gè)對(duì)象!)
// Flutter 的對(duì)比
if (identical(oldWidget, newWidget)) {
// ? 直接跳過,連 build() 都不調(diào)用
return;
}
七、總結(jié)
不可變 vs 可變的對(duì)比成本
| 對(duì)比類型 | 不可變 Widget | 可變 Widget |
|---|---|---|
| 引用對(duì)比 | ? O(1) 安全可靠 | ? 不安全(內(nèi)容可能變) |
| 字段對(duì)比 | ? O(字段數(shù)) 淺對(duì)比 | ? O(所有元素) 深對(duì)比 |
| const 優(yōu)化 | ? 編譯時(shí)創(chuàng)建,全局共享 | ? 無(wú)法使用 const |
| 緩存策略 | ? 可以安全緩存 | ? 緩存不可靠 |
關(guān)鍵區(qū)別
// 不可變:可以信任引用
final list = [1, 2, 3];
final widget1 = ImmutableWidget(items: list);
final widget2 = ImmutableWidget(items: list);
identical(widget1.items, widget2.items) // true
// ? 可以確信內(nèi)容相同,因?yàn)椴豢勺?
// 可變:不能信任引用
final list = [1, 2, 3];
final widget1 = MutableWidget(items: list);
list.add(4); // ?? 修改了
final widget2 = MutableWidget(items: list);
identical(widget1.items, widget2.items) // true
// ? 但內(nèi)容已經(jīng)不同了!必須深度對(duì)比
核心理解:
- 不可變 = 可以用引用對(duì)比 = O(1) = 快
- 可變 = 必須深度對(duì)比 = O(n) = 慢
- const = 全局共享 = identical = 最快
這就是為什么 Flutter 堅(jiān)持 Widget 不可變的根本原因!