前言
之前我们介绍了 Dart 中的基本语法和面向对象(OOP)的内容,今天我们将介绍 Dart 中泛型(Generics)以及库的内容。
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第30天,点击查看活动详情
泛型
泛型的用法同 Java 和 TypeScript 中的用法类似,这里只着重说明 dart 中的特性。
使用集合字面量
List 和 map 字面量可以被参数化。参数化字面量和已经认识的所有字面量一样,仅仅是在字面量的开始括号之前添加<type>(对于 List 类型来说)或者添加<keyType, valueType>(对于 map 类型来说)。
var names = <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']);
var nameSet = Set<String>.from(names);
下面的代码创建了一个具有整数键和视图类型值的map映射:
var views = Map<int, View>();
泛型集合及其包含的类型
dart 通用类型被具体化,这意味着它们在运行时携带它们的类型信息。例如,可以测试集合的类型:
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
注意: 相反,Java 中的泛型使用擦除,这意味着泛型类型参数在运行时被删除。在 Java 中,可以测试一个对象是否是一个列表,但不能测试它是否是 String 类型的列表。
限制参数化类型
在实现泛型类型时,可能希望限制其参数的类型,这个时候需要使用 extends 关键字
class Foo<T extends SomeBaseClass> {
// Implementation goes here...
String toString() => "Instance of 'Foo<$T>'";
}
class Extender extends SomeBaseClass {...}
可以使用 SomeBaseClass 或它的任何子类作为泛型参数:
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
当不知道泛型参数时默认会是 extends 的类本身(否则会是 dynamic):
var foo = Foo();
print(foo); // Instance of 'Foo<SomeBaseClass>'
传入其他的非 extends 的类或其子类时会报错:
var foo = Foo<Object>(); // Error
泛型方法
最初,dart 仅仅在类中支持泛型。后来一种称为泛型方法的新语法允许在方法和函数中使用类型参数。
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;
}
用法同 TypeScript 中非常相似,这里不过多介绍。
库和可见性
import 和 library 指令可以帮助创建模块化和可共享的代码库。库不仅提供api,而且包含隐私部分:以下划线(_)开头的标识符仅在库中可见。
可以使用包来分发库,一般使用用不到,在这里就不做多的讲解。
注: 每个 dart 应用程序都是一个库,即使它不使用库指令。
使用库
-
使用
import来指定如何在另一个库的范围中使用来自一个库的命名空间。例如,
dart web应用程序通常使用dart:html库,它们可以这样导入:import 'dart:html';导入一个库仅仅需要提供库的 URI。对于内置库,URI具有特定的形式(dart:scheme)。对于其他库,可以使用
文件路径或者包:scheme的形式。包:scheme形式指定包管理器(如 pub 工具(类似 npm))提供的库。例如:import 'package:test/test.dart';注意: URI 表示统一资源标识符。url(统一资源定位器)是一种常见的 URI。
-
使用
library定义这个库的名字,但库的名字并不影响导入,因为import语句用的是字符串Urilibrary person;
指定一个库前缀
如果导入两个具有冲突标识符的库,那么可以为一个或两个库指定一个前缀(命名空间)。
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// Uses Element from lib1.
Element element1 = Element();
// Uses Element from lib2.
lib2.Element element2 = lib2.Element();
库的可见性
如果只想使用库的一部分,可以有选择地导入库,这也是库的可见性。
有两种关键字来表示需要使用的内容:
show:导入的库只能使用 show 后面的变量。hide:导入的库不能使用 hide 后面的变量。
// Import only foo.
import 'package:lib1/lib1.dart' show foo;
// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;
懒加载库
延迟加载(也称为懒加载)允许应用程序在需要时按需加载库。
以下是一些可能使用延迟加载的情况:
- 减少应用程序的初始启动时间。
- 例如,要执行A/B测试——尝试算法的其他实现。
- 加载很少使用的功能,如可选屏幕和对话框。
要延迟加载库,必须首先使用deferred as进行导入。
import 'package:greetings/hello.dart' deferred as hello;
当需要库时,使用库的标识符调用loadLibrary()方法。
Future greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
注; 可以在库上多次调用loadLibrary(),但该库只加载一次。
在使用延迟加载时,请记住以下几点:
- 在导入文件中,递延库的常量不是常量。记住,这些常量在延迟库加载之前是不存在的。
- 不能在导入文件中使用来自延迟库的类型。相反,考虑将接口类型移动到由延迟库和导入文件导入的库。
- dart 隐式地将
loadLibrary()插入到定义使用deferred作为名称空间的名称空间中。函数的作用是:返回一个未来(也就是 JavaScript 中的 Promise)。
库的实现
实现库的适合可以依靠part和part of关键字:
- 为了维护一个库,我们可以把各个功能放到各个
dart文件中 - 但
part of所在文件不能包括import、library等关键字 - 可以包含在
part关键字所在文件中 - 建议避免使用
part和part of语句,因为那样会使代码很难阅读、修改 - 可以多用
library、part加字符串类型的 Uri 类似 include,表示包含某个文件 part of加库名表示该文件属于那个库
// math.dart文件开头
library math;
part 'point.dart';
part 'random.dart';
// point.dart文件开头
part of math;
// random.dart文件开头
part of math;
有关如何实现库包的建议,请参阅创建库包,包括:
- 如何组织库的代码。
- 如何使用
export指令。 - 何时使用
part指令。