前言
dart語(yǔ)言的庫(kù)及其相關(guān)語(yǔ)法是了解dart應(yīng)用代碼組織的基礎(chǔ)寄纵。網(wǎng)上查找的相關(guān)資料往往只是涉及某幾個(gè)點(diǎn),很難有系統(tǒng)性的認(rèn)識(shí)脖苏,這里筆者將結(jié)合一些文檔和個(gè)人實(shí)踐經(jīng)驗(yàn)來(lái)對(duì)dart的庫(kù)及其相關(guān)語(yǔ)法進(jìn)行一個(gè)梳理程拭。
庫(kù)的引入
dart中,任意一個(gè)文件都會(huì)被認(rèn)為是一個(gè)庫(kù)棍潘,盡管其中可能并沒(méi)有library
標(biāo)簽恃鞋,dart庫(kù)目前的引入方式大致有三種:
- 引入dart語(yǔ)言的內(nèi)置庫(kù):
import 'dart:math';
引入內(nèi)置庫(kù)時(shí),在使用的uri中以dart:
開頭
- 引入pub包管理器提供庫(kù):
import 'package:flutter/material.dart';
在引用包管理器提供的庫(kù)時(shí)亦歉,uri中以package
開頭
- 引入本地文件:
import './tools/network.dart';
引用本地文件時(shí)恤浪,uri字符串中直接填寫文件的相對(duì)路徑。
指定庫(kù)的別名
兩個(gè)庫(kù)中如果存在相同的標(biāo)識(shí)符肴楷,在使用時(shí)很有可能會(huì)產(chǎn)生沖突资锰;或者在引入一個(gè)庫(kù)的內(nèi)容的時(shí)候,由于當(dāng)前文件引入的庫(kù)比較多阶祭,導(dǎo)致使用IDE工具提供的標(biāo)識(shí)符名稱聯(lián)想時(shí)绷杜,很有可能出現(xiàn)一些本不是我們想要選取,但是首字母相近的內(nèi)容濒募,影響編碼效率鞭盟,為此我們可以使用給庫(kù)指定別名的方法,來(lái)規(guī)避以上問(wèn)題瑰剃。
import 'package:socket_io_client/socket_io_client.dart' as IO;
class MySocketIO {
IO.Socket mySocket;
MySocketIO(this.mySocket);
}
只引入庫(kù)的部分內(nèi)容
如果只想引入庫(kù)的部分內(nèi)容齿诉,可以使用如下語(yǔ)法:
// Import only foo.
import 'package:lib1/lib1.dart' show foo;
如果想屏蔽庫(kù)中的某些內(nèi)容,不引入這部分:
// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;
關(guān)于part晌姚、library和part of
在具體業(yè)務(wù)中有以下痛點(diǎn):我們?cè)趹?yīng)用中定義了多個(gè)類或者其他方法粤剧,在引用時(shí)我們想只import一個(gè)文件就將相關(guān)內(nèi)容全部導(dǎo)出,如果將所有的類或者方法都放在一個(gè)文件中挥唠,會(huì)導(dǎo)致這個(gè)文件十分龐雜抵恋,不利于后續(xù)維護(hù)。為了解決這個(gè)問(wèn)題宝磨,我們可以使用part
弧关、library
和part of
來(lái)組織我們的代碼。
假設(shè)我們的存放公共類和方法的文件為為global.dart,其內(nèi)容可按如下方法組織:
// 定義庫(kù)的名字
library global;
// 文件中引用的公共包
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:i_chat/tools/utils.dart';
import 'package:shared_preferences/shared_preferences.dart';
import './tools/network.dart';
import 'package:dio/dio.dart';
import 'dart:math';
import 'package:provider/provider.dart';
import 'package:socket_io_client/socket_io_client.dart' as IO;
// 組成這個(gè)庫(kù)的其他文件
part './model/User.dart';
part './model/FriendInfo.dart';
part './model/Message.dart';
// ...其他業(yè)務(wù)代碼
在文件的開頭使用library
標(biāo)識(shí)符定義庫(kù)的名字唤锉,這也是其他子文件與其耦合起來(lái)的關(guān)鍵世囊,part
標(biāo)識(shí)符指明組成這個(gè)庫(kù)的其他文件。需要注意的是窿祥,part
部分一定要在import部分的后面株憾。
子文件的組織方式如下,以./model/FriendInfo.dart
為例:
// 指明與其關(guān)聯(lián)的父庫(kù)
part of global;
// 定義其他內(nèi)容
class FriendInfo {
...
}
在子文件的開頭晒衩,使用part of
標(biāo)識(shí)符嗤瞎,后跟父庫(kù)的名字墙歪,來(lái)指明從屬關(guān)系,注意子文件中不需要引入父庫(kù)中已經(jīng)引入的依賴猫胁。
在寫其他業(yè)務(wù)邏輯代碼的時(shí)候只需要直接引入global.dart
文件即可:
import './global.dart';
延遲加載或者異步加載
延遲加載一個(gè)庫(kù)時(shí)箱亿,要使用deferred as
來(lái)進(jìn)行導(dǎo)入:
import 'package:greetings/hello.dart' deferred as hello;
在使用時(shí)跛锌,需要通用調(diào)用loadLibrary()
來(lái)加載對(duì)應(yīng)的內(nèi)容
Future greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
盡管你可能在項(xiàng)目中多次調(diào)用loadLibrary()
來(lái)加載一個(gè)庫(kù)弃秆,但是這個(gè)庫(kù)也只會(huì)被加載一次。
編寫一個(gè)庫(kù)
庫(kù)是代碼復(fù)用和邏輯模塊化的絕佳手段髓帽。庫(kù)是以包的形式被創(chuàng)造和分發(fā)的菠赚。dart語(yǔ)言有兩種類型的包:包含本地庫(kù)的應(yīng)用包(application packages)和庫(kù)包(library packages).
- 應(yīng)用包通常會(huì)依賴其他的包,但是絕不會(huì)有自引用郑藏,應(yīng)用包的反面就是庫(kù)包
- 庫(kù)包是其他包的依賴對(duì)象衡查,他們自己也會(huì)依賴其他包,亦有可能會(huì)自引用必盖,它們中往往含有會(huì)直接運(yùn)行的腳本拌牲,庫(kù)包的反面就是應(yīng)用包
編寫一個(gè)庫(kù)包
下圖展示了一個(gè)最簡(jiǎn)單的庫(kù)包組成結(jié)構(gòu):
一個(gè)庫(kù)所需的最簡(jiǎn)內(nèi)容包括:
- pubspec 文件
pubspec.yaml
文件在庫(kù)包和應(yīng)用包中是類似的,二者并沒(méi)有區(qū)別歌粥。 - lib 目錄
正如你直覺感覺的那樣塌忽,庫(kù)的代碼都在lib目錄下,這部分內(nèi)容對(duì)其他包也是可見的失驶。如果需要的話土居,你可以在lib文件夾下創(chuàng)造其他層級(jí)的文件,按照慣例嬉探,邏輯實(shí)現(xiàn)的代碼通常放在lib/src目錄下擦耀。在該目錄下的文件通常被認(rèn)為是私有的。其他的包不應(yīng)引入src目錄下的內(nèi)容從而暴露lib/scr中的API,正確的使用方法是從lib目錄下的其他文件中引出內(nèi)容涩堤。
組織一個(gè)庫(kù)包
當(dāng)你創(chuàng)建稱為迷你庫(kù)的小型獨(dú)立庫(kù)時(shí)眷蜓,庫(kù)包最容易維護(hù)、擴(kuò)展和測(cè)試胎围。在絕大多數(shù)情況下账磺,每一個(gè)類應(yīng)該都以一個(gè)迷你庫(kù)的形式存在,除非兩個(gè)類之間深度耦合痊远。
為了引出一個(gè)庫(kù)中的公共api,建議在lib目錄下創(chuàng)建一個(gè)'main'文件垮抗,方便使用者僅僅通過(guò)應(yīng)用單文件來(lái)獲取庫(kù)中的所有功能。lib目錄下也有可能包含其他可引入的庫(kù)碧聪。例如冒版,如果你的庫(kù)可以跨平臺(tái)工作,但是你創(chuàng)建了兩個(gè)不同的子文件分別依賴dart:io和datr:html逞姿。部分包引用了不同的庫(kù)辞嗡,在引用這部分內(nèi)容時(shí)需要給他們添加前綴捆等。 接下來(lái)我們觀察一個(gè)真實(shí)的庫(kù)包:shelf,這個(gè)包提供了使用Dart語(yǔ)法創(chuàng)建庫(kù)的服務(wù)器的方法,下圖是其的結(jié)構(gòu):
在lib目錄下的主文件shelf.dart暴露了lib/src下的其他文件中的內(nèi)容給使用者:
export 'src/cascade.dart';
export 'src/handler.dart';
export 'src/handlers/logger.dart';
export 'src/hijack_exception.dart';
export 'src/middleware.dart';
export 'src/pipeline.dart';
export 'src/request.dart';
export 'src/response.dart';
export 'src/server.dart';
export 'src/server_handler.dart';
shelf包還包括一個(gè)迷你庫(kù)续室,shelf_io,他對(duì)dart:io中的http請(qǐng)求體進(jìn)行了簡(jiǎn)單的封裝栋烤。
引入庫(kù)文件
當(dāng)你引用一個(gè)庫(kù)文件的時(shí)候,你可以使用package:
指令來(lái)指定該文件的URI挺狰。
import 'package:utilities/utilities.dart';
對(duì)于引用的文件和被引明郭,文件當(dāng)兩個(gè)文件都在lib內(nèi)部時(shí),或者當(dāng)兩個(gè)文件都在lib外部時(shí)丰泊,可以使用相對(duì)路徑導(dǎo)入庫(kù)薯定。當(dāng)其中一個(gè)文件在lib目錄內(nèi)或者外部時(shí),你必須使用package:
瞳购。當(dāng)你舉棋不定的時(shí)候话侄,可以直接會(huì)用package:
,這種語(yǔ)法在兩種情況下都可用。 下面的圖展示了如何分別從lib目錄和網(wǎng)絡(luò)引入lib/foo/a.dart
:
提供其他文件(測(cè)試代碼学赛、命令行工具)
一個(gè)設(shè)計(jì)良好的庫(kù)要便于測(cè)試年堆。我們推薦你使用test包來(lái)編寫測(cè)試用例,你可以把測(cè)試代碼放在包的頂級(jí)目錄中的test
文件夾下盏浇。
如果你給用戶創(chuàng)建了命令行工具变丧,請(qǐng)將它們放在bin
文件夾下,以便用戶可以直接通過(guò)pub global activate命令來(lái)使用命令行工具缠捌。將命令行工具寫在executables section以便用戶可以直接調(diào)用命令行代碼而無(wú)需調(diào)用pub global run方法.
任何庫(kù)自己私有的工具函數(shù)或代碼(你不想暴露給使用者),你可以將它們放在tool
文件夾下锄贷。 關(guān)于其他你想要的推送到Pub站點(diǎn)的文件,例如README和CHANGELOG等等曼月,你可以在Publishing a Pageage中查閱具體內(nèi)容谊却。
給庫(kù)編寫注釋
你可以使用dartdoc工具來(lái)給你的庫(kù)添加API注釋。Dartdoc將會(huì)解析你的源碼哑芹,找到其中通過(guò)注釋語(yǔ)法標(biāo)記的內(nèi)容炎辨,標(biāo)記示例如下:
/// The event handler responsible for updating the badge in the UI.
void updateBadge() {
...
}
開源一個(gè)庫(kù)
如果你想要開源一個(gè)庫(kù),建議你將其分享在Pub site.可以使用pub publish
命令來(lái)上傳或者更新一個(gè)庫(kù)聪姿。pub site不僅僅存儲(chǔ)你的庫(kù)碴萧,它同時(shí)也會(huì)自動(dòng)生成并且保存的你庫(kù)的api引用文檔。
為了確保你的包的API文檔生成正確末购,你可以遵循以下步驟:
- 在你推送你的包之前破喻,運(yùn)行dartdoc工具確保你的文檔生成正確并且展示符合預(yù)期
- 在你推送你的包之后,檢查Vesion tab去報(bào)你的文檔生成正確
- 如果文檔生成失敗盟榴,檢查dartdoc的輸出
總結(jié)
-
import 'dart:xxx';
引入Dart標(biāo)準(zhǔn)庫(kù) -
import 'xxx/xxx.dart';
引入絕對(duì)路徑的Dart文件 -
import 'package:xxx/xxx.dart';
引入Pub倉(cāng)庫(kù)pub.dev(或者pub.flutter-io.cn)中的第三方庫(kù) -
import 'package:project/xxx/xxx.dart';
引入自定義的dart文件 -
import 'xxx' show compute1曹质,compute2
只導(dǎo)入compute1,compute2 -
import 'xxx' hide compute3
除了compute都引入 -
import 'xxx' as compute4
將庫(kù)重命名,當(dāng)有名字沖突時(shí) -
library compute5;
定義庫(kù)名稱 -
part of compute6;
表示文件屬于某個(gè)庫(kù)
文件導(dǎo)入順序(從上到下依次)
dart sdk 內(nèi)的庫(kù)
flutter內(nèi)的庫(kù)
第三方庫(kù)
自己的庫(kù)(文件)
相對(duì)路徑引用
e.g.
import 'dart:io';
import 'package:material/material.dart';
import 'package:dio/dio.dart';
import 'package:project/common/uitls.dart';
import 'xxx/xxx/xxx/xxx.dart';