一,空值
不要将变量明确初始化为null
。
如果变量具有不可为null的类型,则如果在未明确初始化变量之前尝试使用它,则Dart会报告编译错误。如果该变量可为空,则将null
为您隐式初始化为。Dart中没有“未初始化的内存”的概念,也不需要显式地将变量初始化null
为“安全”。
好的写法
Item? bestItem;
差的写法
Item? bestItem = null;
请勿使用明确的默认值null
。
如果将可为空的参数设置为可选参数,但未给其提供默认值,则该语言将隐式null
用作默认值,因此无需编写该参数。
考虑将可为空的字段复制到局部变量以启用类型提升。
检查可为空的变量不等于将其null
提升为不可为空的类型。这样,您就可以访问变量中的成员,并将其传递给需要非空类型的函数。不幸的是,提升仅适用于局部变量和参数,因此不会提升字段和顶级变量。
解决此问题的一种模式是将字段的值复制到局部变量。对该变量的Null检查确实会提升,因此您可以放心地将其视为不可为空。
好的写法:
class UploadException {
final Response? response;
UploadException([this.response]);
@override
String toString() {
var response = this.response;
if (response != null) {
return "Could not complete upload to ${response.url} "
"(error code ${response.errorCode}): ${response.reason}.";
}
return "Could not upload (no response).";
}
}
与使用!
字段或顶级变量的每个位置相比,复制到局部变量可以更清洁,更安全:
差的写法:
class UploadException {
final Response? response;
UploadException([this.response]);
@override
String toString() {
if (response != null) {
return "Could not complete upload to ${response!.url} "
"(error code ${response!.errorCode}): ${response!.reason}.";
}
return "Could not upload (no response).";
}
}
二,字符串
不要使用相邻的字符串来连接字符串文字。
如果您有两个字符串文字(不是值,而是实际引用的文字形式),则无需使用+
它们来串联它们。就像在C和C ++中一样,只需将它们彼此相邻放置即可。这是制作单个一行不适合的长字符串的好方法。
好的写法
raiseAlarm(
'ERROR: Parts of the spaceship are on fire. Other '
'parts are overrun by martians. Unclear which are which.');
差的写法
raiseAlarm('ERROR: Parts of the spaceship are on fire. Other ' +
'parts are overrun by martians. Unclear which are which.');
优选使用插值来组成字符串和值。
好的写法
'Hello, $name! You are ${year - birth} years old.';
差的写法
'Hello, ' + name + '! You are ' + (year - birth).toString() + ' y...';
避免在不需要时在插值中使用花括号。
如果要插入一个简单标识符,而不是紧随其后的是更多字母数字文本,{}
则应省略。
好的写法
var greeting = 'Hi, $name! I love your ${decade}s costume.';
差的写法
var greeting = 'Hi, ${name}! I love your ${decade}s costume.';
三、集合类型
尽可能使用集合字面量。
Dart具有三种核心集合类型:列表,地图和集合。像大多数类一样,Map和Set类具有未命名的构造函数。但是,由于这些集合的使用频率很高,因此Dart具有更好的内置语法来创建它们:
好的写法
var points = <Point>[];
var addresses = <String, Address>{};
var counts = <int>{};
差的写法
var addresses = Map<String, Address>();
var counts = Set<int>();
集合字面量在Dart中特别强大,因为它们使您能够访问散布运算符 (...
),以包括其他集合的内容,以及是否以及在构建内容时执行控制流:
好的写法
var arguments = [
...options,
command,
...?modeFlags,
for (var path in filePaths)
if (path.endsWith('.dart'))
path.replaceAll('.dart', '.js')
];
差的写法
var arguments = <String>[];
arguments.addAll(options);
arguments.add(command);
if (modeFlags != null) arguments.addAll(modeFlags);
arguments.addAll(filePaths
.where((path) => path.endsWith('.dart'))
.map((path) => path.replaceAll('.dart', '.js')));
不要用.length
看集合是否为空。
可迭代合同不要求集合知道其长度或能够在恒定时间内提供它。 仅查看集合中是否包含任何内容而调用.length可能会非常缓慢。
而是有更快,更易读的getter:.isEmpty和.isNotEmpty。 使用不需要您否定结果的结果。
好的写法
if (lunchBox.isEmpty) return 'so hungry...';
if (words.isNotEmpty) return words.join(' ');
差的写法
if (lunchBox.length == 0) return 'so hungry...';
if (!words.isEmpty) return words.join(' ');
避免Iterable.forEach()在
函数遍历
forEach()
函数在JavaScript中得到了广泛使用,因为内置 for-in
循环无法实现您通常想要的功能。在Dart中,如果要遍历序列,惯用的方法是使用循环。
好的写法
for (var person in people) {
...
}
差的写法
people.forEach((person) {
...
});
另请注意,始终可以使用Map.forEach()
。不是可迭代的,因此该指南不适用。
除非您打算更改结果的类型,否则请勿使用List.from()
。
给定一个List,有两种显而易见的方法来产生一个包含相同元素的新List:
var copy1 = iterable.toList();
var copy2 = List.from(iterable);
明显的区别是第一个较短。的重要 区别在于,第一个保留原始对象的类型的参数:
好的写法:
// Creates a List<int>:
var iterable = [1, 2, 3];
// Prints "List<int>":
print(iterable.toList().runtimeType);
差的写法
// Creates a List<int>:
var iterable = [1, 2, 3];
// Prints "List<dynamic>":
print(List.from(iterable).runtimeType);
如果要更改类型,则调用List.from()
非常有用:
var numbers = [1, 2.3, 4]; // List<num>.
numbers.removeAt(1); // Now it only contains integers.
var ints = List<int>.from(numbers);
但是,如果您的目标只是复制可迭代对象并保留其原始类型,或者您不关心该类型,请使用toList()
。
使用whereType()
按类型过滤集合。
假设您有一个包含对象混合的列表,而您只想从中获取整数。您可以这样使用where()
:
差的写法
var objects = [1, "a", 2, "b", 3];
var ints = objects.where((e) => e is int);
这很冗长,但更糟糕的是,它返回一个可能不是您想要的类型的Iterable。在此处的示例中,Iterable<Object>
即使您可能想要一个,它也会返回一个,Iterable<int>
因为这是您要对其进行过滤的类型。
有时您会看到通过添加cast()
以下代码来“纠正”上述错误的代码:
差的写法
var objects = [1, "a", 2, "b", 3];
var ints = objects.where((e) => e is int).cast<int>();
这很冗长,并导致创建两个包装器,并具有两层间接和冗余运行时检查。幸运的是,核心库具有whereType()
用于此确切用例的方法:
好的写法
var objects = [1, "a", 2, "b", 3];
var ints = objects.whereType<int>();
当附近的操作可以使用时,请勿使用cast()
。
通常,当您处理可迭代项或流时,会对它进行多次转换。 最后,您想产生一个带有特定类型参数的对象。 而不是附加对cast()的调用,请查看现有转换之一是否可以更改类型。
如果您已经在调用toList(),则将其替换为对List <T> .from()的调用,其中T是您想要的结果列表的类型。
好的写法
var stuff = <dynamic>[1, 2];
var ints = List<int>.from(stuff);
差的写法
var stuff = <dynamic>[1, 2];
var ints = stuff.toList().cast<int>();
如果要调用map(),请为其提供一个显式类型参数,以便它生成所需类型的可迭代对象。 类型推断通常基于传递给map()的函数为您选择正确的类型,但有时您需要明确。
好的写法
var stuff = <dynamic>[1, 2];
var reciprocals = stuff.map<double>((n) => 1 / n);
差的写法
var stuff = <dynamic>[1, 2];
var reciprocals = stuff.map((n) => 1 / n).cast<double>();
避免使用cast()。
这是先前规则的较简单的概括。有时没有附近的操作可用于固定某些对象的类型。即使这样,在可能的情况下,也请避免使用cast()“更改”集合的类型。
最好选择以下任一选项:
用正确的类型创建它。更改首次创建集合的代码,以使其具有正确的类型。
在访问时投射元素。如果您立即遍历集合,请在迭代中强制转换每个元素。
使用List.from()进行强制转换。如果您最终将访问集合中的大多数元素,并且不需要原始活动对象支持该对象,请使用List.from()对其进行转换。
cast()方法返回一个惰性集合,该集合检查每个操作上的元素类型。如果仅对几个元素执行几个操作,那么懒惰就可以了。但是在许多情况下,延迟验证和包装的开销超过了好处。
这是使用正确类型创建它的示例:
好的写法
List<int> singletonList(int value) {
var list = <int>[];
list.add(value);
return list;
}
差的写法
List<int> singletonList(int value) {
var list = []; // List<dynamic>.
list.add(value);
return list.cast<int>();
}
这是在访问时强制转换每个元素:
好的写法
void printEvens(List<Object> objects) {
// We happen to know the list only contains ints.
for (var n in objects) {
if ((n as int).isEven) print(n);
}
}
差的写法
void printEvens(List<Object> objects) {
// We happen to know the list only contains ints.
for (var n in objects.cast<int>()) {
if (n.isEven) print(n);
}
}
这里需要使用List.from()
:
好的写法
int median(List<Object> objects) {
// We happen to know the list only contains ints.
var ints = List<int>.from(objects);
ints.sort();
return ints[ints.length ~/ 2];
}
差的写法
int median(List<Object> objects) {
// We happen to know the list only contains ints.
var ints = objects.cast<int>();
ints.sort();
return ints[ints.length ~/ 2];
}
四、函数
在Dart中,甚至函数都是对象
tear-off时不要创建匿名函数。
如果您引用对象上的方法但省略括号,则Dart会给您“撕除”(tear-off),该闭包采用与该方法相同的参数,并在调用该方法时调用它。
如果您有一个函数使用与传递给它的参数相同的参数来调用方法,则无需手动将调用包装在匿名函数中。
使用=
将命名参数与其默认值分开。
由于遗留原因,Dart允许:
和都=
将命名参数用作默认值分隔符。为了与可选的位置参数保持一致,请使用 =
。
好的写法
void insert(Object item, {int at = 0}) { ... }
差的写法
void insert(Object item, {int at: 0}) { ... }