Dart 包与库管理
当项目规模增长时,将代码拆分为多个文件和模块是必不可少的。
本章介绍 Dart 的包管理系统 pub、依赖配置文件 pubspec.yaml、import/export 语法以及如何创建自定义库。
pubspec.yaml 配置
pubspec.yaml 是每个 Dart 项目的核心配置文件,声明了项目的元数据和依赖。
一个典型的 pubspec.yaml 文件结构如下:
实例
name: my_dart_app # 项目名称(必填,小写+下划线)
description: 一个 Dart 示例项目 # 项目描述
version: 1.0.0 # 版本号
# publish_to: none # 如果不想发布到 pub.dev,取消这行注释
environment:
sdk: '>=3.0.0 <4.0.0' # Dart SDK 版本范围
dependencies:
# 项目运行时依赖的第三方包
http: ^1.1.0 # HTTP 客户端
path: ^1.8.0 # 路径工具
dev_dependencies:
# 仅开发时需要的依赖(测试、代码检查等)
test: ^1.24.0 # 测试框架
lints: ^2.0.0 # 官方 lint 规则
# 可选:可执行文件入口
# executables:
# my_app: main
版本号的写法说明:
| 写法 | 含义 | 示例 |
|---|---|---|
| ^1.1.0 | 兼容 1.1.0 到 2.0.0(不含) | 最常用,推荐使用 |
| 1.1.0 | 精确版本 | 不太灵活 |
| >=1.1.0 <1.5.0 | 版本范围 | 需要精确控制时使用 |
| any | 任意版本 | 不推荐 |
^ 符号(caret)是 Dart 的默认版本约束方式。^1.1.0 等价于 >=1.1.0 <2.0.0。这意味着可以自动升级次版本和补丁版本,但不能升级主版本(主版本升级可能包含破坏性变更)。
安装依赖
$ dart pub get # 安装依赖 $ dart pub upgrade # 升级依赖到最新兼容版本 $ dart pub outdated # 查看哪些依赖有更新
pub.dev 查找依赖
pub.dev 是 Dart 和 Flutter 的官方包仓库,类似于 npm(JavaScript)或 PyPI(Python)。
你可以在 https://pub.dev 上搜索和浏览数以万计的 Dart 包。
常用的第三方包举例:
| 包名 | 用途 | 说明 |
|---|---|---|
| http | HTTP 请求 | 官方维护的 HTTP 客户端 |
| path | 路径操作 | 跨平台的路径处理工具 |
| test | 单元测试 | 官方测试框架 |
| json_serializable | JSON 序列化 | 自动生成 JSON 转换代码 |
| dio | HTTP 客户端 | 比 http 更强大的第三方 HTTP 库 |
| riverpod | 状态管理 | Flutter 项目的热门状态管理方案 |
实例
添加 http 包并使用:
// http: ^1.1.0
// 然后运行:dart pub get
import 'package:http/http.dart' as http;
void main() async {
// 发送一个 GET 请求
var url = Uri.parse('https://www.runoob.com');
var response = await http.get(url);
print('状态码: ${response.statusCode}');
print('响应体长度: ${response.body.length} 字符');
}
import 导入语法
import 用于在一个 Dart 文件中引入其他库的代码。
Dart 支持多种导入方式,每种适用于不同的场景。
导入 Dart 内置库
实例
import 'dart:math'; // 数学库(随机数、三角函数等)
import 'dart:convert'; // 编码转换库(JSON、Base64 等)
import 'dart:io'; // I/O 库(文件、网络等)
void main() {
// 使用 dart:math 中的函数
print('圆周率: $pi');
print('2 的 10 次方: ${pow(2, 10)}');
// 使用 dart:convert 中的函数
var jsonStr = '{"name": "runoob", "age": 10}';
var decoded = jsonDecode(jsonStr);
print('解析后的 JSON: $decoded');
print('用户名: ${decoded['name']}');
}
圆周率: 3.141592653589793
2 的 10 次方: 1024
解析后的 JSON: {name: runoob, age: 10}
用户名: runoob
导入第三方包
实例
import 'package:http/http.dart';
import 'package:path/path.dart' as p;
导入本地文件
实例
import 'src/utils.dart'; // 同目录下的子目录
import '../models/user.dart'; // 上级目录中的文件
import 'constants.dart'; // 同目录下的文件
import 的修饰符
| 修饰符 | 语法 | 用途 |
|---|---|---|
| 前缀(as) | import 'lib.dart' as myLib; | 给库起别名,避免命名冲突 |
| 只导入部分(show) | import 'lib.dart' show foo, bar; | 只导入指定的名称 |
| 排除部分(hide) | import 'lib.dart' hide foo; | 导入除指定名称外的所有内容 |
| 延迟加载(deferred as) | import 'lib.dart' deferred as lib; | 按需加载,减少启动时间 |
实例
各种导入修饰符的实际用法:
import 'dart:math' as math; // 前缀导入:使用 math.pow() 而非 pow()
import 'dart:math' show pi, sqrt; // 只导入 pi 和 sqrt
import 'dart:math' hide Random; // 导入除 Random 外的所有内容
void main() {
// 标准导入
print('sin(0) = ${sin(0)}');
// 前缀导入:需要通过前缀访问
print('cos(0) = ${math.cos(0)}');
// show 导入:只能使用 pi 和 sqrt
print('π = $pi');
print('sqrt(16) = ${sqrt(16)}');
// print(sin(0)); // 错误:sin 没有被导入
// hide 导入:Random 不可用,其他都可以
print('max(3, 7) = ${max(3, 7)}');
// Random(); // 错误:Random 被排除了
}
sin(0) = 0.0 cos(0) = 1.0 π = 3.141592653589793 sqrt(16) = 4.0 max(3, 7) = 7
show 和 hide 不只是为了便利——它们也是代码质量的工具。使用 show 可以让依赖关系更清晰:你能一眼看出这个文件使用了库中的哪些符号。
export 导出语法
export 用于将其他库的公开 API 重新暴露出去,主要用于创建聚合库。
假设你有以下文件结构:
lib/ ├── my_package.dart # 入口文件(聚合导出) ├── src/ │ ├── models/ │ │ └── user.dart # User 类 │ ├── services/ │ │ └── api_service.dart # API 服务 │ └── utils/ │ └── helpers.dart # 工具函数
实例
使用 export 创建聚合库入口:
// 聚合导出:将所有公开 API 统一暴露
export 'src/models/user.dart';
export 'src/services/api_service.dart';
// 只导出 helpers 中的部分内容
export 'src/utils/helpers.dart' show formatDate, validateEmail;
这样使用者只需要导入一个文件:
实例
import 'package:my_package/my_package.dart';
void main() {
// 可以直接使用所有 export 的类
var user = User('runoob');
var api = ApiService();
}
自定义库的创建
创建一个自定义库不需要特殊语法——每个 Dart 文件就是一个库。
但你可以使用 library 关键字明确声明库名,以及使用 part/part of 拆分大型库。
使用 library 声明库名
实例
// library 声明库名(可选,但有助于文档化)
library calculator;
/// 加法运算
int add(int a, int b) => a + b;
/// 减法运算
int subtract(int a, int b) => a - b;
/// 乘法运算
int multiply(int a, int b) => a * b;
/// 除法运算,除数为 0 时抛出异常
double divide(int a, int b) {
if (b == 0) {
throw ArgumentError('除数不能为 0');
}
return a / b;
}
使用 part 拆分大型库
当一个库的代码太长时,可以用 part 将其拆分为多个物理文件,但它们在逻辑上仍然属于同一个库。
实例
主文件声明 part:
// 主库文件
library user_system;
// 声明组成这个库的其他文件
part 'src/user_model.dart';
part 'src/user_service.dart';
part 'src/user_validator.dart';
// 库级别的公开 API
String libraryVersion = '1.0.0';
实例
part 文件声明 part of:
// part of 声明自己属于哪个库
part of '../user_system.dart';
// 可以访问主库中的 libraryVersion
class User {
String name;
User(this.name);
void printVersion() {
print('RUNOOB User 模块版本: $libraryVersion');
}
}
part/part of 在现代 Dart 开发中不常用,多数团队更倾向于使用 import/export 来组织代码。part 的缺点是 part 文件之间共享所有私有成员,破坏了封装性。除非有明确需求,否则推荐使用 import/export。
Dart 核心库速览
| 库名 | 导入方式 | 主要功能 |
|---|---|---|
| dart:core | 自动导入 | 基础类型、集合、异常等 |
| dart:math | import 'dart:math'; | 数学常量和函数 |
| dart:convert | import 'dart:convert'; | JSON、UTF-8、Base64 编解码 |
| dart:io | import 'dart:io'; | 文件、网络、进程操作 |
| dart:async | import 'dart:async'; | Future、Stream 等异步工具 |
| dart:collection | import 'dart:collection'; | 更多集合类型(Queue 等) |
| dart:developer | import 'dart:developer'; | 调试和性能分析工具 |
dart:core 是自动导入的,你不需要写 import 'dart:core' 就能使用 int、String、List、Map 等基础类型。
