在本教程中,我分享了我的顶级Dart技巧和窍门,这些技巧将改善你作为Flutter开发者的编码风格。
您可以使用它们来编写更简洁有效的代码,并充分利用Dart语言。
准备好了吗?让我们开始吧!
1.你知道吗?Dart支持字符串乘法。
这里有一个简单的程序,展示了如何用字符串乘法打印一棵圣诞树。
void main() {
for (var i = 1; i <= 5; i++) {
print('🎄' * i);
}
}
// Output:
// 🎄
// 🎄🎄
// 🎄🎄🎄
// 🎄🎄🎄🎄
// 🎄🎄🎄🎄🎄
很酷吧? 😉
你可以用它来检查一个长的字符串在一个Text widget里面是怎样的。
Text('You have pushed the button this many times:' * 5)
2.需要同时执行多个Future?使用Future.wait。
考虑一下这个模拟的API类,它告诉我们COVID案例的最新数字。
// Mock API class
class CovidAPI {
Future<int> getCases() => Future.value(1000);
Future<int> getRecovered() => Future.value(100);
Future<int> getDeaths() => Future.value(10);
}
要并发地执行所有这些期货,请使用Future.wait 。这需要一个列表或期货,并返回一个列表的未来。
final api = CovidAPI();
final values = await Future.wait([
api.getCases(),
api.getRecovered(),
api.getDeaths(),
]);
print(values); // [1000, 100, 10]
当这些期货是独立的,并且它们不需要按顺序执行时,这是理想的做法。
3.在你的Dart类中实现一个 "调用 "方法,使它们像函数一样可以调用。
下面是一个例子:PasswordValidator 类。
class PasswordValidator {
bool call(String password) {
return password.length > 10;
}
}
因为该方法被命名为call ,我们可以声明一个类实例,并像使用方法一样使用它。
final validator = PasswordValidator();
// can use it like this:
validator('test');
validator('test1234');
// no need to use it like this:
validator.call('not-so-frozen-arctic');
4.需要调用一个回调,但只有在它不为空的情况下才能调用?使用"?.call() "语法。
假设我们有一个自定义的widget类,当某个事件发生时应该调用一个onDragCompleted 回调。
class CustomDraggable extends StatelessWidget {
const CustomDraggable({Key key, this.onDragCompleted}) : super(key: key);
final VoidCallback? onDragCompleted;
void _dragComplete() {
// TODO: Implement me
}
@override
Widget build(BuildContext context) {/*...*/}
}
为了调用该回调,我们可以写这样的代码。
void _dragComplete() {
if (onDragCompleted != null) {
onDragCompleted();
}
}
但是有一个更简单的方法(注意使用?. )。
Future<void> _dragComplete() async {
onDragCompleted?.call();
}
5.使用匿名函数和作为参数的函数
在Dart中,函数是一流的公民,可以作为参数传递给其他函数。
这里有一些代码,定义了一个匿名函数,并将其分配给一个sayHi 变量。
void main() {
final sayHi = (name) => 'Hi, $name';
welcome(sayHi, 'Andrea');
}
void welcome(String Function(String) greet,
String name) {
print(greet(name));
print('Welcome to this course');
}
然后,sayHi 被传递给一个welcome 函数,该函数接收一个Function 参数并使用它来问候用户。
String Function(String) 是一个函数类型,它接受一个 参数并返回一个 。因为上面的匿名函数有相同的String String签名,它可以直接作为参数传递,也可以通过 变量传递。sayHi
这种编码方式在使用函数运算符(如map,where, 和reduce )时很常见。
例如,这里有一个简单的函数来计算一个数字的平方。
int square(int value) {
// just a simple example
// could be a complex function with a lot of code
return value * value;
}
给定一个值的列表,我们可以通过映射来获得平方。
const values = [1, 2, 3];
values.map(square).toList();
这里我们把square 作为一个参数,因为它的签名正是map运算符所期望的。这意味着我们不需要用一个匿名函数来扩展它。
values.map((value) => square(value)).toList();
6.你可以在列表、集合和地图中使用 collection-if 和 spreads
当你把你的用户界面写成代码时,collection-if和spreads是非常有用的。
但是你知道吗,你也可以在地图中使用它们?
考虑一下这个例子。
const addRatings = true;
const restaurant = {
'name' : 'Pizza Mario',
'cuisine': 'Italian',
if (addRatings) ...{
'avgRating': 4.3,
'numRatings': 5,
}
};
这里我们声明了一个restaurant 地图,并且只在addRatings 是true 的情况下添加avgRating 和numRatings 的键值对。因为我们要添加一个以上的键值对,所以我们需要使用传播操作符(...)。
7.需要以空安全的方式遍历一个地图?使用`. entries`。
假设你有这个地图。
const timeSpent = <String, double>{
'Blogging': 10.5,
'YouTube': 30.5,
'Courses': 75.2,
};
这里你可以写一个循环,使用所有的键值对运行一些代码。
for (var entry in timeSpent.entries) {
// do something with keys and values
print('${entry.key}: ${entry.value}');
}
通过在entries 变量上的迭代,你可以以空安全的方式访问所有的键值对。
这比这更简明,更不容易出错。
for (var key in timeSpent.keys) {
final value = timeSpent[key]!;
print('$key: $value');
}
上面的代码在读取数值时需要使用断言操作符(!),因为Dart不能保证某个键的数值存在。
8.使用命名的构造函数和初始化器列表以获得更符合人体工程学的API。
假设你想声明一个代表温度值的类。
你可以通过两个命名的构造函数使你的类的API不含糊,并同时支持摄氏和华氏温度。
class Temperature {
Temperature.celsius(this.celsius);
Temperature.fahrenheit(double fahrenheit)
: celsius = (fahrenheit - 32) / 1.8;
double celsius;
}
这个类只需要一个存储变量来表示温度,并使用一个初始化器列表来将华氏温度转换为摄氏温度。
这意味着你可以像这样声明温度值。
final temp1 = Temperature.celsius(30);
final temp2 = Temperature.fahrenheit(90);
9.获取器和设置器
在上面的Temperature 类中,celsius 被声明为一个存储变量。
但是用户可能更喜欢用华氏温度来获取或设置温度。
这很容易通过getters和setters来实现,它们允许你定义计算变量。下面是更新后的类。
class Temperature {
Temperature.celsius(this.celsius);
Temperature.fahrenheit(double fahrenheit)
: celsius = (fahrenheit - 32) / 1.8;
double celsius;
double get fahrenheit
=> celsius * 1.8 + 32;
set fahrenheit(double fahrenheit)
=> celsius = (fahrenheit - 32) / 1.8;
}
这使得用华氏温度或摄氏温度来获取或设置温度变得很容易。
final temp1 = Temperature.celsius(30);
print(temp1.fahrenheit);
final temp2 = Temperature.fahrenheit(90);
temp2.celsius = 28;
一句话:使用命名的构造函数、getters和setters来改善你的类的设计。
10.对未使用的函数参数使用下划线
在Flutter中,我们经常使用需要函数参数的小部件。一个常见的例子是ListView.builder 。
class MyListView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) => ListTile(
title: Text('all the same'),
),
itemCount: 10,
);
}
}
(context, index) 在这种情况下,我们没有使用itemBuilder 中的参数。因此我们可以用下划线代替它们。
ListView.builder(
itemBuilder: (_, __) => ListTile(
title: Text('all the same'),
),
itemCount: 10,
)
注意:这两个参数是不同的(_ 和__),因为它们是独立的标识符。
11.需要一个只能被实例化一次的类(又称单子)?使用一个静态实例变量和一个私有构造函数。
单子的最重要的属性是在你的整个程序中只能有一个实例。这对于像文件系统这样的东西的建模很有用。
// file_system.dart
class FileSystem {
FileSystem._();
static final instance = FileSystem._();
}
要在Dart中创建一个单子,你可以声明一个命名的构造函数,并使用_ 语法将其私有化。
然后你可以用它来创建你的类的一个静态最终实例。
因此,其他文件中的任何代码都只能通过instance 变量来访问这个类。
// some_other_file.dart
final fs = FileSystem.instance;
// do something with fs
注意:如果你不小心,单子会导致很多问题。在使用它们之前,请确保你了解其弊端。
12.需要一个独特项目的集合?使用一个集合而不是一个列表。
Dart中最常用的集合类型是List 。
但是列表可以有重复的项目,有时这不是我们想要的。
const citiesList = [
'London',
'Paris',
'Rome',
'London',
];
只要我们需要一个唯一值的集合,我们就可以使用一个Set (注意使用final )。
// set is final, compiles
final citiesSet = {
'London',
'Paris',
'Rome',
'London', // Two elements in a set literal shouldn't be equal
};
上面的代码产生了一个警告,因为London 被包含了两次。如果我们尝试用一个const 集合做同样的事情,我们会得到一个错误,我们的代码不能编译。
// set is const, doesn't compile
const citiesSet = {
'London',
'Paris',
'Rome',
'London', // Two elements in a constant set literal can't be equal
};
当我们使用集合时,我们可以访问有用的API,如union,difference, 和intersection 。
citiesSet.union({'Delhi', 'Moscow'});
citiesSet.difference({'London', 'Madrid'});
citiesSet.intersection({'London', 'Berlin'});
一句话:当你创建一个集合时,问问你自己是否希望它的项目是唯一的,并考虑使用一个集合。
13.如何使用try, on, catch, rethrow, finally
try 和 ,在处理基于Future的API时是非常理想的,这些API可能在出错时抛出一个异常。catch
这里有一个完整的例子,展示了如何充分利用它们的作用。
Future<void> printWeather() async {
try {
final api = WeatherApiClient();
final weather = await api.getWeather('London');
print(weather);
} on SocketException catch (_) {
print('Could not fetch data. Check your connection.');
} on WeatherApiException catch (e) {
print(e.message);
} catch (e, st) {
print('Error: $e\nStack trace: $st');
rethrow;
} finally {
print('Done');
}
}
一些注意事项。
- 你可以添加多个
on子句来处理不同类型的异常。 - 你可以有一个后备的
catch子句来处理所有不符合上述任何类型的异常。 - 你可以使用
rethrow语句将当前的异常抛到调用堆栈中**,同时保留堆栈跟踪**。 - 你可以使用
finally,在Future完成后运行一些代码,不管它是成功还是失败。
如果你正在使用或设计一些基于Future的API,请确保根据需要处理异常。
14.常见的Future构造函数
DartFuture 类带有一些方便的工厂构造函数。Future.delayed,Future.value 和Future.error 。
我们可以使用Future.delayed 来创建一个等待一定延迟的Future 。第二个参数是一个(可选)匿名函数,你可以用它来完成一个值或抛出一个错误。
await Future.delayed(Duration(seconds: 2), () => 'Latte');
但有时我们想创建一个立即完成的Future 。
await Future.value('Cappuccino');
await Future.error(Exception('Out of milk'));
我们可以使用Future.value 来成功完成一个值,或者使用Future.error 来完成一个错误。
你可以使用这些构造函数来模拟来自你的基于Future的API的响应。在你的测试代码中编写模拟类时,这很有用。
15.常见的流构造函数
Stream类还附带了一些方便的构造函数。下面是最常见的。
Stream.fromIterable([1, 2, 3]);
Stream.value(10);
Stream.empty();
Stream.error(Exception('something went wrong'));
Stream.fromFuture(Future.delayed(Duration(seconds: 1), () => 42));
Stream.periodic(Duration(seconds: 1), (index) => index);
- 使用
Stream.fromIterable,从一个值的列表中创建一个Stream。 - 如果你只有一个值,请使用
Stream.value。 - 使用
Stream.empty来创建一个空流。 - 使用
Stream.error,创建一个包含错误值的流。 - 使用
Stream.fromFuture创建一个只包含一个值的流,并且该值在未来完成时可以使用。 - 使用
Stream.periodic来创建一个周期性的事件流。你可以指定一个Duration作为事件之间的时间间隔,并指定一个匿名函数来生成流中的每个值的索引。
16.同步和异步生成器
在Dart中,我们可以将同步生成器定义为一个返回Iterable 的函数。
Iterable<int> count(int n) sync* {
for (var i = 1; i <= n; i++) {
yield i;
}
}
这使用了sync* 语法。在这个函数中,我们可以 "生成 "或yield 多个值。当函数完成时,这些值将作为一个Iterable 返回。
另一方面,一个异步生成器是一个返回Stream 的函数。
Stream<int> countStream(int n) async* {
for (var i = 1; i <= n; i++) {
yield i;
}
}
这使用了这种async* 语法。在函数内部,我们可以像在同步情况下一样,yield 值。
但如果我们愿意,我们可以在基于Future的API上await ,因为这是一个异步发生器。
Stream<int> countStream(int n) async* {
for (var i = 1; i <= n; i++) {
// dummy delay - this could be a network request
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
结语
我希望您喜欢我为Flutter开发者提供的顶级Dart技巧和窍门。
我对您的挑战是什么?
编码愉快!