0%

Dart入门四

1.泛型

Dart中泛型 (或 参数化) 类型使用<…> 符号表示。通常情况下,使用一个字母来代表类型参数, 例如 E, T, S, K, 和 V 等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 使用集合字面量
var names = <String>['Seth', 'Kathy', 'Lars'];
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
var pages = <String, String>{
'index.html': 'Homepage',
'robots.txt': 'Hints for web robots',
'humans.txt': 'We are people, not machines'
};

// 运行时中的泛型集合
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
var views = Map<int, View>(); // _InternalLinkedHashMap<int, View>

泛型函数

1
2
3
4
5
6
T first<T>(List<T> ts) {
// Do some initial work or error checking, then...
T tmp = ts[0];
// Do some additional checking or processing...
return tmp;
}

类的泛型

1
2
3
4
5
6
class Location<T> {
T x;
T y;

Location(this.x, this.y);
}

使用泛型类型的时候, 可以使用 extends 实现参数类型的限制。如果我们希望类型只能是num类型:

1
2
3
4
5
6
class Location<T extends num> {
T x;
T y;

Location(this.x, this.y);
}

2.库

importlibrary 指令可以用来创建一个模块化的,可共享的代码库。 库不仅提供了 API ,而且对代码起到了封装的作用: 以下划线 (_) 开头的标识符仅在库内可见。
库可以通过包来分发。有关 pub(集成在SDK中的包管理器)的信息,请参考 Pub Package 和 Asset Manager

每个 Dart 应用程序都是一个库 ,虽然没有使用 library 指令。

库的导入

通过 import 指定一个库命名空间中的内如如何在另一个库中使用。对于内置库,URI 拥有自己特殊的dart:前缀 。 对于其他的库,用系统文件路径或者package:前缀指定由包管理器(如 pub 工具)提供的库。例如,Dart Web应用程序通常使用 dart:html 库,它们可以像这样导入:

1
2
3
import 'dart:html';

import 'package:flutter/material.dart';

指定库前缀

如果导入两个存在冲突标识符的库, 则可以为这两个库,或者其中一个指定前缀。 例如,如果 library1 和 library2 都有一个 Element 类, 那么可以通过下面的方式处理:

1
2
3
4
5
6
7
8
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;

// 使用 lib1 中的 Element。
Element element1 = Element();

// 使用 lib2 中的 Element。
lib2.Element element2 = lib2.Element();

导入库的一部分

如果你只使用库的一部分功能,则可以选择需要导入的 内容。例如:

1
2
3
4
5
// Import only foo.
import 'package:lib1/lib1.dart' show foo;

// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;

延迟加载库

Deferred loading (也称之为 lazy loading) 可以让应用在需要的时候再加载库。 下面是一些使用延迟加载库的场景:

  • 减少 APP 的启动时间。
  • 执行 A/B 测试,例如 尝试各种算法的 不同实现。
  • 加载很少使用的功能,例如可选的屏幕和对话框。

要延迟加载一个库,需要先使用 deferred as 来导入:

1
2
3
4
5
6
7
import 'package:greetings/hello.dart' deferred as hello;

// 当需要使用的时候,使用库标识符调用 loadLibrary() 函数来加载库
Future greet() async {
await hello.loadLibrary();
hello.printGreeting();
}

在一个库上你可以多次调用 loadLibrary() 函数。但是该库只是载入一次。
Dart 隐含的把 loadLibrary() 函数导入到使用 deferred as 的命名空间 中。 loadLibrary() 方法返回一个 Future。

实现库

组织库的源文件y有export 命令和part 命令两种方式,官方不推荐使用part关键字
export关键字
将每一个dart文件作为库文件,使用export关键字在某个库文件中单独导入。

mathUtils.dart文件

1
2
3
int sum(int num1, int num2) {
return num1 + num2;
}

dateUtils.dart文件
1
2
3
4
5
6
7
8
9
String dateFormat(DateTime date) {
return "2019-12-17";
}
`utils.dart`文件
```dart
library utils;

export "mathUtils.dart";
export "dateUtils.dart";

test_libary.dart文件
1
2
3
4
5
6
import "lib/utils.dart";

main(List<String> args) {
print(sum(10, 20));
print(dateFormat(DateTime.now()));
}

Dart异步模型

Dart是单线程的,在开发中,我们经常会遇到一些耗时的操作需要完成,比如网络请求、文件读取等等。处理耗时操作一般有两种方式:

  • 处理方式一: 多线程,比如Java、我们普遍的做法是开启一个新的线程(Thread),在新的线程中完成这些异步的操作,再通过线程间通信的方式,将拿到的数据传递给主线程。
  • 处理方式二: 单线程+事件循环,比如JavaScript、Dart都是基于单线程加事件循环来完成耗时操作的处理。

不过单线程如何能进行耗时的操作呢?
单线程模型中主要就是在维护着一个事件循环(Event Loop)。

  • 事实上事件循环并不复杂,它就是将需要处理的一系列事件(包括点击事件、IO事件、网络事件)放在一个事件队列(Event Queue)中。
  • 不断的从事件队列(Event Queue)中取出事件,并执行其对应需要执行的代码块,直到事件队列清空位置。

Dart 库中包含许多返回 FutureStream 对象的函数。这些函数在设置完耗时任务(例如 I/O 曹组)后, 就立即返回了,不会等待耗任务完成。 使用 asyncawait 关键字实现异步编程。 可以让你像编写同步代码一样实现异步操作。

Feature

可以将 Future 理解成 ES6 中的 Promise

在 Dart 库中随处可见 Future 对象,通常异步函数返回的对象就是一个 Future。 当一个 future 完成执行后,future 中的值就已经可以使用了。当 future 执行完成后,then() 中的代码会被执行。例如, HttpRequest.getString() 返回一个 future 对象,因为 HTTP 请求可能需要一段时间。 当 Future 完成并且保证字符串值有效后,使用 then() 来执行你需要的代码:

1
2
3
4
5
6
HttpRequest.getString(url).then((String result) {
print(result);
}).catchError((e) {
// Handle or ignore the error.
print(e);
});

使用 catchError() 来处理一些 Future 对象可能抛出的错误或者异常。then().catchError() 组合是 try-catch 的异步版本。

重要: 确保调用 catchError() 方式在 then() 的结果上,而不是在原来的 Future 对象上调用。 否则的话,catchError() 就只能处理原来 Future 对象抛出的异常, 而无法处理 then() 代码里面的异常。

等待多个 Future
有时代码逻辑需要调用多个异步函数, 并等待它们全部完成后再继续执行。 使用 Future.wait() 静态方法管理多个 Future 以及等待它们完成:

1
2
3
4
5
6
7
8
9
10
Future deleteLotsOfFiles() async =>  ...
Future copyLotsOfFiles() async => ...
Future checksumLotsOfOtherFiles() async => ...

await Future.wait([
deleteLotsOfFiles(),
copyLotsOfFiles(),
checksumLotsOfOtherFiles(),
]);
print('Done with all the long steps!');

async、await

可以将 async、await 理解成 ES6 中的 async、await
使用 async、await 关键字的代码是异步的,虽然看起来有点像同步代码。

async
函数体被 async 标示符标记的函数,即是一个异步函数。 将 async 关键字添加到函数使其返回Future。

1
2
3
4
5
// 同步函数,它返回一个 String
String lookUpVersion() => '1.0.0';

// 例如,将来的实现将非常耗时,将其更改为异步函数,返回值是 Future 。
Future<String> lookUpVersion() async => '1.0.0';

await
使用 await 表达式 会阻塞代码的执行,直到需要的对象返回为止。在 await 表达式 中, 表达式 的值通常是一个 Future 对象; 如果不是,这是表达式的值会被自动包装成一个 Future 对象。 Future 对象指明返回一个对象的承诺(promise)。 await 表达式 执行的结果为这个返回的对象。 要使用 await , 代码必须在 异步函数(使用 async 标记的函数)中:
1
2
3
4
Future checkVersion() async {
var version = await lookUpVersion();
// Do something with version
}

注意,函数体不需要使用Future API。 如有必要, Dart 会创建 Future 对象。
如果函数没有返回有效值, 需要设置其返回类型为 Future<void>

在一个异步函数中可以多次使用 await 。 例如,下面代码中等待了三次函数结果:

1
2
3
var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);

使用 trycatch, 和 finally 来处理代码中使用 await 导致的错误。

1
2
3
4
5
try {
version = await deleteLotsOfFiles();
} catch (e) {
// React to inability to look up the version
}

Feture和async、awiit区别

在直接使用 Future API 前,首先应该考虑 await 来替代。 代码中使用 await 表达式会比直接使用 Future API 更容易理解。

1
2
3
4
5
6
7
8
Future result = costlyQuery(url);
result
.then((value) => expensiveWork(value))
.then((_) => lengthyComputation())
.then((_) => print('Done!'))
.catchError((exception) {
/* Handle exception... */
});

在上面的示例中,方法按下面顺序执行:

  1. costlyQuery()
  2. expensiveWork()
  3. lengthyComputation()

下面是使用 await 编写的等效代码:

1
2
3
4
5
6
7
8
try {
final value = await costlyQuery(url);
await expensiveWork(value);
await lengthyComputation();
print('Done!');
} catch (e) {
/* Handle exception... */
}

使用async、awiit的方式,可以用类似同步代码的写法实现异步操作,推荐使用 asyncawait 关键字实现异步编程。

赞赏是最好的支持