特意给大家带来我在开发中总结的 dart 相干的技巧
1. 你晓得吗?Dart 反对字符串乘法。
这是一个简略的程序,显示如何应用字符串乘法打印圣诞树:
void main() {for (var i = 1; i <= 5; i++) {print('🎄' * i);
}
}
// Output:
// 🎄
// 🎄🎄
// 🎄🎄🎄
// 🎄🎄🎄🎄
// 🎄🎄🎄🎄🎄
是不是很酷?😉
您能够应用它来查看长字符串如何适宜 Text
小部件:
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);
}
要同时执行所有这些 futures,请应用 Future.wait
. 这须要一个 列表或 futures and returns a future of lists**:
final api = CovidAPI();
final values = await Future.wait([api.getCases(),
api.getRecovered(),
api.getDeaths(),]);
print(values); // [1000, 100, 10]
This is ideal when the futures are independent, and they don’t need to execute sequentially.
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()”语法。
假如咱们有一个自定义小部件类,它应该 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 与 lists, sets AND maps
当您将 UI 作为代码编写时,Collection-if 和 spreads 十分有用。
然而您晓得您也能够将它们与 maps 一起应用吗?
思考这个例子:
const addRatings = true;
const restaurant = {
'name' : 'Pizza Mario',
'cuisine': 'Italian',
if (addRatings) ...{
'avgRating': 4.3,
'numRatings': 5,
}
};
这里咱们申明一个 restaurant
maps,只增加avgRating
和numRatings
键值对,如果 addRatings
是true
。因为咱们要增加多个键值对,所以咱们须要应用扩大运算符 (...
)。
7. 须要以空平安的形式遍历 map 吗?应用.entries
:
假如你有 map:
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. getter 和 setter
在 Temperature
下面的类中,celsius
被申明为存储变量。
然而用户可能更喜爱以华氏度 获取 或设置 温度。
这能够应用 getter 和 setter 轻松实现,它们容许您定义计算变量。这是更新的课程:
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;
底线:应用命名构造函数、getter 和 setter 来改良类的设计。
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
留神:如果您不小心,final 可能会导致许多问题。在应用它们之前,请确保您理解它们的毛病。
12. 须要收集独特的 set?应用汇合而不是列表。
Dart 中最罕用的汇合类型是List
.
然而列表能够有反复的我的项目,有时这不是咱们想要的:
const citiesList = [
'London',
'Paris',
'Rome',
'London',
];
咱们能够 Set
在须要一组惟一值时应用 a(请留神 的应用final
):
// set is final, compiles
final citiesSet = {
'London',
'Paris',
'Rome',
'London', // Two elements in a set literal shouldn't be equal
};
下面的代码生成一个正告,因为 London
被蕴含了两次。如果咱们尝试对const
set 执行雷同的操作,则会收到谬误并且咱们的代码无奈编译:
// 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
并且 catch
在应用基于 Future 的 API 时十分现实,如果呈现问题,这些 API 可能会引发异样。
这是一个残缺的示例,展现了如何充分利用它们:
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
创立周期性的事件流。您能够将 a 指定Duration
为事件之间的工夫距离,并指定一个匿名函数来生成给定其在流中的索引的每个值。
16. 同步和异步生成器
在 Dart 中,咱们能够将 同步 生成器定义为一个返回 的函数Iterable
:
Iterable<int> count(int n) sync* {for (var i = 1; i <= n; i++) {yield i;}
}
这应用 sync*
语法。在函数外部,咱们能够“生成”或 yield
多个值。这些将 Iterable
在函数实现时返回。
另一方面,异步 生成器是一个返回 a 的函数Stream
:
Stream<int> countStream(int n) async* {for (var i = 1; i <= n; i++) {yield i;}
}
这应用此 async*
语法。在函数外部,咱们能够 yield
像在同步状况下一样取值。
然而如果咱们违心,咱们能够应用 await
基于 Future 的 API,因为这是一个 异步 生成器:
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;
}
}