本节目标
- 空安全意味着什么
- 如何迁移代码
- 如何禁用空安全
- 代码规范示例
视频
https://www.bilibili.com/video/bv1g5411c7hF
代码
https://github.com/ducafecat/getx_quick_start
参考
正文
空安全意味着什么
1
| String title = 'ducafecat';
|
1 2
| String? title = 'ducafecat'; String newTitle = title!;
|
1 2
| String? title = 'ducafecat'; bool isEmpty = title?.isEmpty();
|
1 2
| String? title = 'ducafecat'; String newTitle = title ?? 'cat';
|
late
会在运行时检查。所以请您仅在确定它被使用前一定会被初始化的情况下使用
1 2
| late String? title; title = 'ducafecat';
|
类型 |
集合是否可空 |
数据项是否可空 |
List |
no |
no |
List? |
yes |
no |
List<String?> |
no |
yes |
List<String?>? |
yes |
yes |
类型 |
集合是否可空 |
数据项是否可空 |
Map<String, int> |
no |
no* |
Map<String, int>? |
yes |
no* |
Map<String, int?> |
no |
yes |
Map<String, int?>? |
yes |
yes |
*
可能返回空
1 2 3 4 5 6 7 8
| int value = <String, int>{'one': 1}['one'];
int? value = <String, int>{'one': 1}['one'];
int value = <String, int>{'one': 1}['one']!;
|
带来的好处
开启和迁移
1 2
| environment: sdk: ">=2.12.0 <3.0.0"
|
我们强烈建议您按顺序迁移代码,先迁移依赖关系中的处于最末端的依赖。例如,如果 C 依赖了 B,B 依赖了 A,那么应该按照 A -> B -> C 的顺序进行迁移。
1 2 3 4 5
| # Dart 版本是否为 2.12 或更高 > dart --version
# 依赖包的迁移状态 > dart pub outdated --mode=null-safety
|
1 2 3 4 5
| # 该命令会更改您的 pubspec.yaml 文件 > dart pub upgrade --null-safety
# 升级包 > dart pub upgrade
|
禁用空安全
1 2
| > dart --no-sound-null-safety run > flutter run --no-sound-null-safety
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| { "version": "0.2.0", "configurations": [ { "name": "getx_quick_start", "request": "launch", "type": "dart", "program": "lib/main.dart", "args": ["--no-sound-null-safety"] } ] }
|
范例、规范
https://dart.cn/null-safety/understanding-null-safety
1 2 3 4 5 6 7
| makeCoffee(String coffee, [String? dairy]) { if (dairy != null) { print('$coffee with $dairy'); } else { print('Black $coffee'); } }
|
- 顶层变量和静态字段必须包含一个初始化方法。由于它们能在程序里的任何位置被访问到,编译器无法保证它们在被使用前已被赋值。唯一保险的选项是要求其本身包含初始化表达式,以确保产生匹配的类型的值。
1 2 3 4 5
| int topLevel = 0;
class SomeClass { static int staticField = 0; }
|
- 实例的字段也必须在声明时包含初始化方法,可以为常见初始化形式,也可以在实例的构造方法中进行初始化。
1 2 3 4 5 6 7 8
| class SomeClass { int atDeclaration = 0; int initializingFormal; int initializationList;
SomeClass(this.initializingFormal) : initializationList = 0; }
|
- 局部变量的灵活度最高。一个非空的变量 不一定需要 一个初始化方法。
1 2 3 4 5 6 7 8 9 10 11
| int tracingFibonacci(int n) { int result; if (n < 2) { result = n; } else { result = tracingFibonacci(n - 2) + tracingFibonacci(n - 1); }
print(result); return result; }
|
- 流程分析,在这里 Dart 将
object
的类型从它声明的 Object
提升到了 List
。在空安全引入以前,下面的程序无法运行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| bool isEmptyList(Object object) { if (object is List) { return object.isEmpty; } else { return false; } }
->
bool isEmptyList(Object object) { if (object is! List) return false; return object.isEmpty; }
|
1 2 3 4 5 6 7 8 9 10 11
| int tracingFibonacci(int n) { final int result; if (n < 2) { result = n; } else { result = tracingFibonacci(n - 2) + tracingFibonacci(n - 1); }
print(result); return result; }
|
1 2 3 4 5 6
| String checkList(List list) { if (list?.isEmpty) { return 'Got nothing'; } return 'Got something'; }
|
- 懒加载的变量,
late
修饰符是“在运行时而非编译时对变量进行约束”。这就让 late
这个词语约等于 何时 执行对变量的强制约束。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Coffee { String? _temperature;
void heat() { _temperature = 'hot'; } void chill() { _temperature = 'iced'; }
String serve() => _temperature! + ' coffee'; }
->
class Coffee { late String _temperature;
void heat() { _temperature = 'hot'; } void chill() { _temperature = 'iced'; }
String serve() => _temperature + ' coffee'; }
|
late
与 final
结合使用,与普通的 final
字段不同,您不需要在声明或构造时就将其初始化。您可以稍后在运行中的某个地方加载它。但是您只能对其进行 一次 赋值,并且它在运行时会进行校验。
1 2 3 4 5 6 7 8 9
| class Coffee { late final String _temperature;
void heat() { _temperature = 'hot'; } void chill() { _temperature = 'iced'; }
String serve() => _temperature + ' coffee'; }
|
- 毕传参数,这里的所有参数都必须通过命名来传递。参数
a
和 c
是可选的,可以省略。参数 b
和 d
是必需的,调用时必须传递。在这里请注意,是否必需和是否可空无关。
1 2
| function({int? a, required int? b, int? c, required int? d}) {}
|
1 2 3 4 5 6 7 8 9 10
| abstract class Cup { Beverage get contents; set contents(Beverage); }
->
abstract class Cup { abstract Beverage contents; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ListQueue _context; Float32List _buffer; dynamic _readObject;
Vec2D(Map<String, dynamic> object) { _buffer = Float32List.fromList([0.0, 0.0]); _readObject = object['container']; _context = ListQueue<dynamic>(); }
->
final ListQueue _context = ListQueue<dynamic>(); final Float32List _buffer = Float32List.fromList([0.0, 0.0]); final dynamic _readObject;
Vec2D(Map<String, dynamic> object) : _readObject = object['container'];
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| factory StreamReader(dynamic data) { StreamReader reader; if (data is ByteData) { reader = BlockReader(data); } else if (data is Map) { reader = JSONBlockReader(data); } return reader; }
->
factory StreamReader(dynamic data) { if (data is ByteData) { return BlockReader(data); } else if (data is Map) { return JSONBlockReader(data); } else { throw ArgumentError('Unexpected type for data'); } }
|
© 猫哥
https://ducafecat.tech
https://ducafecat.gitee.io
邮箱 ducafecat@gmail.com / 微信 ducafecat / 留言板 disqus