Flutter面试题——面试题整理7

一 面试题汇总

  1. 你能提供一下 SOLID 原则的概述吗?
  2. Dart 中 Object、dynamic 和 var 有何不同?
  3. 什么是 Dart 中 cascade 级联和 extension 扩展运算?
  4. mixin 混入和 interface 接口在 Dart 中有何不同?
  5. Dart 中的空安全是什么?
  6. 你能解释 Dart 中的 Isolate、Event Loop 和 Future 的概念吗?
  7. Flutter 无状态和有状态小部件之间有什么区别,以及 setState() 的作用是什么?
  8. Flutter InheritedWidget 是什么?
  9. 你能解释一下在 Flutter 中 keys 键的作用吗?
  10. 在 Flutter 中,Keys(键)有哪几种类型?

二 面试题解答(仅供参考)

2.1 你能提供一下 SOLID 原则的概述吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
SOLID 原则是面向对象编程中一组设计原则,旨在帮助开发者编写可维护、可扩展、高质量的代码。
它由五个主要原则组成,分别是:

1. S -单一职责原则 (Single Responsibility Principle, SRP)
-定义:每个类应该只有一个职责,也就是只负责一项功能。
-目的:减少类的复杂度,避免类承担过多的职责。如果一个类承担了过多的职责,会使得它变得难以修改和扩展。
-示例:如果一个类既负责数据存储,又负责数据的显示,那么它应该被拆分成两个类,一个负责存储,另一个负责显示。

2. O - 开放/封闭原则 (Open/Closed Principle, OCP)
-定义:软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。
-目的:在需要修改某个功能时,尽量避免修改现有的代码,而是通过扩展现有的类来实现新功能。
-示例:通过继承或者接口实现功能扩展,而不是直接修改原有类的实现。

3. L - 里氏替换原则 (Liskov Substitution Principle, LSP)
-定义:子类对象应该能够替换父类对象,且程序的功能不受影响。
-目的:确保继承关系的合理性,避免子类对父类行为的破坏。
-示例:如果Bird是父类,而Penguin是继承自Bird的子类,那么Penguin不应该破坏Bird类的预期行为(如飞行能力)。

4. I - 接口隔离原则 (Interface Segregation Principle, ISP)
-定义:不应该强迫客户端依赖它不需要的接口。
-目的:将大接口拆分成多个小接口,让实现类只需要关注自己需要的部分。
-示例:一个类不应该实现它不需要的功能,如果一个类不需要某些方法,
应该将这些方法分到不同的接口中,而不是让类去实现这些方法。

5. D - 依赖倒置原则 (Dependency Inversion Principle, DIP)
-定义:高层模块不应该依赖低层模块,二者应该依赖抽象(接口或抽象类)。
具体实现应依赖于抽象,而不是依赖于具体类。
-目的:降低模块之间的耦合度,提高系统的灵活性和可扩展性。
-示例:一个依赖注入(DI)框架可以帮助实现依赖倒置,避免直接依赖于具体实现类,而是依赖于接口或抽象类。


总结:
SOLID 原则是面向对象设计中的核心理念,旨在通过合理划分职责、灵活扩展和依赖注入等方式,
创建高内聚、低耦合的代码结构,提升代码的可维护性、可扩展性和可测试性。

2.2 Dart 中 Object、dynamic 和 var 有何不同?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
在 Dart 中,Object、dynamic 和 var 都涉及到类型的定义,但它们在类型检查和赋值时有着不同的行为和用途。
下面是它们的区别

1. Object
-定义:Object 是 Dart 中所有类的基类,几乎所有的对象都是 Object 类型的子类。
-类型检查:声明为 Object 的变量可以接受任何类型的对象(包括自定义类),但访问时只能使用 Object 类的方法。
如果要调用特定类型的方法,必须进行类型转换。
-使用场景:当你不确定变量的具体类型时,但又希望它是一个对象时,使用 Object。
-示例
Object obj = 'Hello, Dart'; // 这可以是任何类型
print(obj.toString()); // 可以调用 Object 类的通用方法


2. dynamic
-定义:dynamic 是 Dart 中的特殊类型,它表示一个可以动态改变类型的变量,表示完全没有静态类型检查的变量。
-类型检查:dynamic 类型的变量可以赋任何类型的值,并且在编译时不会进行类型检查。
类型检查会在运行时进行,这可能会导致运行时错误。
-使用场景:当你希望一个变量能够接收任何类型的值,且不需要在编译时检查时使用。
-示例
dynamic obj = 'Hello, Dart'; // 可以随时更改为其他类型
obj = 123; // 可以赋值为不同的类型
print(obj.length); // 如果 obj 是 int 类型,会在运行时出错

3. var
-定义:var 是 Dart 中的一种声明变量的方式,它的类型会根据右侧赋值的类型自动推导出来。
-类型检查:一旦给 var 赋值后,变量的类型就被固定了,不能再赋值为不同类型的值。
它的类型由赋值时决定,所以相当于具有静态类型,但仍然需要在编译时进行类型推导。
-使用场景:当你希望变量类型由编译器自动推导并且在之后不再改变时,使用 var
-示例
var name = 'Dart'; // 自动推导为 String 类型
name = 123; // 编译时错误,因为 name 被推导为 String 类型

总结

类型 定义 类型检查 特点
Object 所有类的基类,可以存储任何类型的对象 可以存储任何类型的对象,但只能调用 Object 的方法。 类型固定,无法直接调用其他类特有的方法,需类型转换。
dynamic 完全动态类型,可以存储任何类型的值,并且没有编译时类型检查。 没有编译时类型检查,类型检查在运行时进行。 允许任意类型,且没有类型安全,容易导致运行时错误。
var 类型由右侧赋值推导确定,一旦确定后不可更改 类型由赋值推导,且不允许改变类型。 类型自动推导,确保类型安全,但类型固定。

2.3 什么是 Dart 中 cascade 级联和 extension 扩展运算?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
在 Dart 中,cascade(级联) 和 extension(扩展) 是两个用于提高代码可读性和简洁性的特性。
它们各自有不同的用途,帮助你更有效地操作对象和扩展类功能。

1. Cascade(级联运算符)
-定义:级联运算符 .. 允许你在同一个对象上连续调用多个方法或设置多个属性,而无需重复写对象的名称。
-作用:简化代码、减少重复,使得代码更加简洁和易读。
-使用场景:当你需要对同一个对象执行多个操作时,使用级联可以让代码更加简洁
-示例
class Person {
String name;
int age;

Person({required this.name, required this.age});

void sayHello() {
print('Hello, my name is $name.');
}

void haveBirthday() {
age++;
}
}

void main() {
var person = Person(name: 'John', age: 30);

// 使用级联运算符
person
..sayHello() // 调用方法
..haveBirthday() // 调用方法
..name = 'Jane'; // 设置属性

print('Updated name: ${person.name}, Age: ${person.age}');
}
解释:
-使用 .. 级联运算符,可以在一个对象person上依次调用sayHello()和haveBirthday(),并修改name属性。
-级联运算符帮助我们避免多次引用 person 对象,简化了代码。

2. Extension(扩展运算符)
-定义:扩展运算符 extension 允许你为现有类添加方法、getter、setter 或操作符,而无需修改类的原始代码。
它可以让你在不改变原有类的情况下,扩展其功能。
-作用:通过扩展现有类,使得该类可以拥有新的功能,符合开放/封闭原则,不修改原有类代码。
-使用场景:当你需要为现有类添加额外的功能或方法,但不能直接修改该类的源代码时,可以使用扩展。
-示例
extension StringReversal on String {
// 为 String 类型扩展一个 reverse 方法
String get reverse {
return this.split('').reversed.join('');
}
}

void main() {
String message = 'Dart';

// 使用扩展方法
print(message.reverse); // 输出: traD
}
解释:
-通过extension StringReversal,我们为String类型添加了一个reverse方法,该方法返回字符串的反转结果。
-扩展方法可以直接通过 . 运算符来调用,就像原生方法一样。

总结

特性 定义 用途 示例
Cascade 使用 .. 运算符在同一对象上连续调用方法或设置属性 提高代码简洁性,减少重复引用对象 person..sayHello()..haveBirthday()..name = 'Jane';
Extension 使用 extension 为现有类添加方法、属性等,而不修改原类代码 扩展现有类的功能,符合开放/封闭原则 extension StringReversal on String { String get reverse {...} }

2.4 mixin 混入和 interface 接口在 Dart 中有何不同?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
在 Dart 中,mixin(混入) 和 interface(接口) 都用于实现类之间的复用和功能共享,
但它们的工作原理和使用方式有所不同。下面是它们的主要区别:

ChatGPT 说:
在 Dart 中,mixin(混入) 和 interface(接口) 都用于实现类之间的复用和功能共享,
但它们的工作原理和使用方式有所不同。下面是它们的主要区别:

1. Mixin(混入)
-定义:Mixin 是一种类复用的机制,允许你将一个类的行为(方法和属性)混入到另一个类中,而无需通过继承。
Dart 中的 mixin 关键字用于定义混入类。
-使用场景:当你希望复用某些功能到多个类时,mixin 比传统继承更加灵活,
因为它允许在多个类之间共享代码,而不需要继承整个类。
-特点:
--不能实例化混入类。
--允许混入多个类,类似于多重继承,但不会带来多重继承的复杂性。
--可以包含方法和字段。

2. Interface(接口)
-定义:接口是用于定义类应遵循的行为规范。
Dart 中的所有类都可以作为接口使用。当一个类实现接口时,它必须实现接口中定义的所有方法。
-使用场景:当你需要指定类应实现某些功能或行为时,可以通过接口来规定类的结构。
-特点:
-Dart 中没有单独的 interface 关键字,任何类都可以作为接口。
-类实现接口时,必须实现接口中定义的所有方法。
-接口仅包含方法签名,没有实现。

总结

特性 mixin(混入) interface(接口)
定义 用于类之间共享代码的机制,可以在多个类中混入功能。 用于定义类应该遵循的行为规范,强制实现类提供方法实现。
继承 可以通过 with 关键字混入多个类 使用 implements 关键字实现接口,强制实现所有方法。
方法实现 可以包含方法的实现(有具体代码)。 只包含方法签名,没有具体的实现(必须由实现类提供实现)。
多重继承 支持多重混入,可以在一个类中混入多个 mixin。 一个类只能实现多个接口,但不能继承多个接口的实现。
实例化 不能实例化 mixin 类,只能通过 with 关键字混入 接口不能被实例化,但可以用作实现类的类型检查。

2.5 Dart 中的空安全是什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
Dart 中的空安全(Null Safety) 是 Dart 2.12 引入的特性,
旨在减少程序中出现null引用错误(NullPointerException)的可能性,从而提高代码的稳定性和安全性。

空安全特性使得类型系统能够在编译时确保 null 值的安全使用,避免了空引用的错误。
通过空安全,Dart 强制要求开发者明确指定哪些变量可以为空,哪些不能为空。

1. 如何启用空安全?
-在 Dart 2.12 及之后的版本中,空安全是默认启用的。
-你可以通过 dart migrate 命令来迁移已有项目到支持空安全的版本

2.空安全的主要概念
-非空类型(Non-nullable Types):
默认情况下,Dart 中的所有类型都 不能为 null。
也就是说,一个变量在声明时,如果不显式指定为可空类型,它就不能为 null。
-可空类型(Nullable Types):如果希望一个变量可以为 null,需要在类型后面加上 ?,这表示该变量是可空的。
-示例
int a = 5; // a 不能为 null
a = null; // 错误:a 不能赋值为 null

int? b = 5; // b 可以为 null
b = null; // 正确:b 可以为 null

3. 常见的空安全特性:
-强制声明非空:默认情况下,Dart 不允许将 null 赋给非空类型变量。
-使用 ? 声明可空类型:可以通过在类型后加 ? 来表示该变量允许为 null。
-空安全操作符:
--?(可空类型操作符):访问可空对象时,如果对象为 null,不会抛出异常,而是返回 null。
示例
String? name = null;
int? length = name?.length; // length 为 null,不会抛出异常

--!(强制解包操作符):如果你确定一个可空类型的变量不为 null,可以使用 ! 操作符强制解包。
若变量确实为 null,则会抛出异常。
示例
String? name = "Dart";
print(name!.length); // 强制解包,如果 name 为 null 会抛出异常

--late 关键字:用于声明一个非空变量,且该变量在初始化时不需要立即赋值。
late 表示变量稍后会被初始化,避免编译器警告。
示例
late String description;
description = "Dart is awesome!"; // 稍后初始化

4. 空安全的好处:
-编译时检测 null 错误:Dart 在编译时就能检查出是否有可能出现 null 引用错误,从而避免运行时的崩溃。
-明确变量的可空性:通过 ? 和 ! 等符号,Dart强制开发者明确区分哪些变量可以为 null,
哪些不能为 null,这使得代码更加安全和易于理解。

5.总结:
-空安全 是 Dart 2.12 引入的特性,目的是避免空引用错误。
-非空类型 默认情况下不能为 null,可空类型 需要在类型后加 ? 来表示。
-空安全操作符(如 ?、!、late)允许开发者在编译时保证代码的 null 安全,提升代码的健壮性。

2.6 你能解释 Dart 中的 Isolate、Event Loop 和 Future 的概念吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
在 Dart 中,Isolate、Event Loop 和 Future 是处理并发、异步操作和事件循环的核心概念。
它们在 Flutter 中也起着至关重要的作用,尤其在涉及 UI 更新、异步操作和多线程时。
让我们逐一了解这三个概念。

1. Isolate(隔离体)
-定义:Isolate 是 Dart 中实现并发的基本单位,它是一个独立的执行单元,拥有自己的内存空间、事件队列和堆栈。
每个 Isolate 之间互不共享内存,因此它们之间的通信只能通过消息传递来完成。
-特点:
--独立的内存空间:每个 Isolate 都有自己的内存堆和堆栈,不能直接访问其他 Isolate 的内存。
--并行执行:可以在多个 Isolate 上并行执行任务,适合执行计算密集型任务,避免阻塞主线程。
--消息传递:由于 Isolate 之间没有共享内存,它们通过 SendPort 和 ReceivePort 进行消息通信。

2. Event Loop(事件循环)
-定义:Event Loop 是 Dart 和 JavaScript 中的核心概念,它负责管理所有的异步操作和任务队列。
简单来说,事件循环机制允许 Dart 程序在等待某些操作(如 I/O、定时器等)时不中断主线程的执行,
而是通过事件队列将这些操作推送到后续执行。

-工作原理:
--Dart 程序开始时,主线程会启动事件循环。
--当遇到异步操作(如 Future、Stream)时,操作会被放入事件队列。
--事件循环会不断检查队列,并按顺序执行队列中的任务。

3. Future(未来值)
-定义:Future 是 Dart 中表示异步操作结果的对象。
它代表一个可能还没有完成的操作,并提供了 then、catchError 等方法来处理异步结果。

-工作原理:
--Future 用于表示将来某个时间点会获得一个结果或错误,它允许开发者以非阻塞的方式处理异步任务。
--Future 通过 then、catchError 或 async/await 来处理结果。

小总结:
-Isolate 用于并发执行独立任务,避免主线程阻塞。
-Event Loop 负责管理异步任务的执行顺序,保证 UI 线程不被阻塞。
-Future 用于表示异步任务的结果,允许开发者以非阻塞的方式处理异步操作。

总结

概念 定义 特点 使用场景
Isolate 独立的执行单元,拥有自己的内存空间和事件队列,适合并行执行任务。 不共享内存、通过消息传递通信、可并行执行任务。 适用于并发计算密集型任务,避免 UI 阻塞。
Event Loop 负责管理和调度异步操作的循环机制。 异步任务放入事件队列,按顺序执行。 异步任务放入事件队列,按顺序执行。
Future 表示一个异步操作的结果,表示未来某个时间会获得一个结果或错误。 提供 thencatchError 等方法处理异步结果。 适用于处理异步操作,如 I/O、网络请求、延时操作等。

2.7 Flutter 无状态和有状态小部件之间有什么区别,以及 setState() 的作用是什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
在Flutter中,StatelessWidget(无状态小部件)和StatefulWidget(有状态小部件)是两种基本的Widget类型,它们之间有很大的区别。
setState() 是与 StatefulWidget 相关的一个方法,用于触发小部件的重建。
下面是它们的详细区别和 setState() 的作用。

1. StatelessWidget(无状态小部件)
定义:StatelessWidget 是不持有任何状态的 Widget。
也就是说,它的外观和行为完全由它的 输入数据(即构造函数中的参数)决定,一旦构建完成就不会发生改变。

特点:
-不可变:无状态小部件是静态的,不会随着时间变化而改变。它们的 UI 内容完全依赖传入的参数(构造函数参数)。
-性能高:由于没有状态变化,它们的重建相对简单,性能较好。

2. StatefulWidget(有状态小部件)
定义:StatefulWidget 是持有状态的 Widget。
它的 UI 会随着状态的变化而更新,可以响应用户交互或外部数据变化。

特点:
-可变状态:有状态小部件的外观和行为可以根据状态的变化动态更新。
-State 类:每个 StatefulWidget 都有一个关联的 State 类,负责管理和维护状态。
-重建:当状态发生变化时,可以通过调用 setState() 来重新构建 UI。

3. setState() 的作用
定义:setState() 是StatefulWidget中的一个方法,用于通知Flutter框架状态发生变化,并触发该小部件的重建。

作用:
-当状态发生变化时,调用 setState() 会标记该 Widget 为“脏”的,需要重新构建。
-setState() 内部会调用 build() 方法,从而重新构建 UI,更新屏幕显示。
-只有在 StatefulWidget 中才能使用 setState(),因为它依赖于 State 类来管理状态。

小总结:
-StatelessWidget 适用于没有状态变化的组件,它的 UI 是静态的。
-StatefulWidget 适用于需要根据用户交互或数据变化更新 UI 的组件,
状态发生变化时可以通过 setState() 更新 UI

总结

特性 StatelessWidget(无状态小部件) StatefulWidget(有状态小部件)
状态 不持有状态,UI 基于外部传入的数据而构建。 持有状态,UI 根据内部状态的变化进行更新。
可变性 不会随着时间变化而改变。 可以根据状态变化进行重建,UI 会动态更新。
构建方法 build() 方法只依赖构造函数中的数据。 build() 方法依赖状态管理,并通过 setState() 来触发更新。
性能 性能较高,因为不需要重建 UI。 性能相对较低,需要通过 setState() 重建 UI。
使用场景 适用于静态的、不可变的 UI 组件,如显示信息、无交互的组件。 适用于需要根据用户操作或外部事件动态更新的 UI 组件。

2.8 Flutter InheritedWidget 是什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Flutter 中的 InheritedWidget 是一种特殊的 Widget,用于在 Widget 树中向下传递数据。
它的主要作用是提供一种机制,
使得树中下层的 Widget 可以访问到祖先 Widget 中的共享数据,而无需通过构造函数一层层传递数据。

1. InheritedWidget 的基本概念
-InheritedWidget 允许在Widget树中共享数据,并通过Widget树的 上下文(BuildContext)来访问这些数据。
-当 InheritedWidget 中的数据发生变化时,它会通知依赖它的子 Widget 重新构建,以便更新数据。
-它适用于在 Widget 树中传递跨多个子树的数据,常见于应用级的状态管理、主题、路由等场景。

2. InheritedWidget 的工作原理
-共享数据:InheritedWidget 会将共享数据存储在它的 child 属性下,并通过 of 方法让子 Widget 访问。
-依赖更新:当InheritedWidget中的数据变化时,它会标记所有依赖它的子Widget为“脏”,触发这些子Widget的重建。

3. 使用 InheritedWidget
-要使用 InheritedWidget,需要创建一个自定义的继承自 InheritedWidget 的类,
并重写 updateShouldNotify() 方法,决定何时通知子 Widget 更新。
-子 Widget 通过 InheritedWidget.of(context) 获取共享的数据。

4. InheritedWidget 的常见使用场景
-主题管理:InheritedWidget 用于在 Widget 树中传递主题数据(如颜色、字体等),
使得所有子 Widget 可以访问主题并相应更新。
-应用配置:可以用来传递应用级别的配置或状态,如用户信息、设置、语言等。
-状态管理:一些轻量级的状态管理方案(例如 Provider)就是基于 InheritedWidget 实现的

5. 优缺点
优点:
高效:
InheritedWidget 仅会在其数据发生变化时,通知依赖它的 Widget 更新,不会影响树中其他不相关的 Widget。
简洁:
对于较小的应用,使用 InheritedWidget 可以很方便地实现跨 Widget 树的数据共享。

缺点:
复杂性:
对于复杂的状态管理或频繁变化的数据,InheritedWidget 的使用可能会显得较为繁琐,且手动管理依赖可能不够方便。

性能:如果不合理使用,过多的 InheritedWidget 嵌套可能会导致性能问题,特别是在需要频繁重建树的情况下。

总结:
-InheritedWidget 是 Flutter 中用于跨 Widget 树共享数据的一种机制,常用于应用级别的状态管理。
-它通过 updateShouldNotify() 来判断是否需要通知子 Widget 重新构建。
-of(context) 是获取数据的方式,它可以帮助子 Widget 获取来自父 Widget 的共享数据。
-适合传递全局性的数据,但对于复杂的状态管理,可能需要更强大的工具(如 Provider、Riverpod)来管理。

2.9 你能解释一下在 Flutter 中 keys 键的作用吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
在 Flutter 中,Key 是一个非常重要的概念,用于标识和区分不同的 Widget 或者 Widget 的状态。
它特别在 Widget 树重建、UI 更新以及列表渲染时扮演着重要的角色。
通过使用 Key,Flutter 能够更高效地重建和重新排序 Widget 树。

1. Key 的作用
Key 主要有以下几种作用:

-标识唯一性:
Key可以唯一标识一个Widget或者它的状态,帮助Flutter 在 Widget 树重建时识别并保持不同 Widget 的状态。

-保持状态:
在列表或动态 UI 中,Key使得Flutter能够正确地重新绑定 Widget 的状态,避免在 Widget 重新构建时丢失状态。

-优化性能:通过提供 Key,Flutter 可以避免不必要的 Widget 重建和重新排序,提高性能。

2. 使用场景
-列表中的元素:
当我们有一个动态列表(例如使用 ListView 渲染的列表)时,Key 可以帮助 Flutter 确定哪个元素发生了变化,
从而有效地重用现有的 Widget,而不是重新创建一个新的。

-局部更新:在需要保持局部状态或局部更新时(例如在一个Form中),通过Key可以确保某个子Widget的状态不会丢失。


3. Key 在 Widget 重建中的作用
Key 在 Flutter 中的最重要的作用是在 Widget 树重建时,
帮助 Flutter 确定哪些 Widget 需要保留状态,哪些 Widget 需要重新创建。

-没有 Key 时,如果一个父 Widget 重新构建,它会销毁其所有的子 Widget,然后重新创建一遍。
这可能会导致一些子 Widget 的状态丢失。
-使用 Key 时,Flutter 能够根据 Key 来识别出哪些子 Widget 是相同的,从而保留它们的状态或更新它们。

4. 为什么需要 Key?
-状态管理:当 Widget 树发生变化时,Key 可以帮助 Flutter 保持 Widget 的状态和属性,避免不必要的重建。
-优化渲染性能:使用 Key 能够帮助 Flutter 更高效地重建界面,只更新必要的部分,减少性能开销。
-列表优化:对于复杂的列表或动态组件,通过 Key 能有效地避免重新构建整个列表,只更新有变化的部分。

2.10 在 Flutter 中,Keys(键)有哪几种类型?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Flutter 中有几种常见的 Key 类型,每种类型的使用场景有所不同

1.GlobalKey:
作用:
GlobalKey 用于标识一个 Widget 的唯一性,并允许跨多个 Widget 树之间访问它。
它能够访问 Widget 树中任何位置的状态,常用于跨组件通信。

使用场景:
当你需要在多个地方访问同一个Widget的状态时,GlobalKey 会非常有用,例如访问 Form 的状态或 Scaffold 状态

2.LocalKey(包括 ValueKey, ObjectKey, UniqueKey):
-作用:
LocalKey 是一种比较轻量的 Key,适用于在同一 Widget 树内区分不同的 Widget。
LocalKey 通常用于对比同级 Widget 是否发生变化。

-ValueKey:基于值来比较 Widget,适用于通过特定数据(如字符串或整数)区分的 Widget。
-ObjectKey:基于对象引用来区分 Widget,适用于依赖于对象的状态。
-UniqueKey:每次创建一个新的 UniqueKey 时,它都会生成一个唯一的标识符,适用于需要完全唯一标识的场景。

总结

Key 类型 描述 适用场景
GlobalKey 用于跨多个 Widget 树访问状态,能够全局访问 Widget 和其状态。 表单、Scaffold、导航等跨界面状态管理。
ValueKey 基于值来判断 Widget 是否相等。 根据数据值(如 ID、字符串)区分 Widget
ObjectKey 基于对象引用来判断 Widget 是否相等。 对象状态管理,如列表中的对象。
UniqueKey 每次创建一个新的、唯一的 Key。 需要确保 Widget 唯一性的场景。

三 参考

  • 狗哥课堂—Flutter 面试题整理 02