Dart 2.13 版現已發布


作者 / Kevin Moore & Michael Thomsen

Dart 2.13 版現已發布,其中新增了類型別名功能,這是目前用戶呼聲第二高的語言功能。Dart 2.13 還改進了 Dart FFI 以及更好的性能,並且我們還為 Dart 提供了新的官方鏡像。本文將為您奉上 2.12 版中推出的空安全功能的最新信息,介紹 2.13 版本的新特性,以及 Docker 和 Google Cloud 對 Dart 后端支持的新消息。另外,還會預告在后續版本中的其他變化。

空安全更新

在今年 3 月份發布的 Dart 2.12 中,我們推出了 健全的空安 全功能。空安全可謂是 Dart 最近推出的一項重要功能,旨在幫助您避免空值錯誤 (這類錯誤經常難以發現),有效提升工作效率。我們希望發布 package 的開發者能夠及時跟進這項發布,更新 pub.dev 上分享的 package 以支持空安全。

我們極其欣喜地看到,在發布后的短短幾個月內,空安全就已被廣泛采用,目前 pub.dev 上前 500 個最受歡迎的 package 中,93% 的 package 已經支持空安全。 在此,謹向如此迅速跟進的所有 package 開發者致以最誠摯的謝意,感謝大家幫助推動整個生態系統不斷向前!

有了這么多 package 支持空安全,您就可以開始考慮着手將自己的應用遷移到使用空安全的環境。要開始遷移,請首先使用 dart pub outdated 檢查應用的依賴項。詳細步驟,請參閱 空安全遷移指南。我們還調整了 dart createflutter create 模板,現在它們在新的應用程序和 package 中默認啟用空安全。

推出類型別名功能

類型別名是 2.13 版中新增的語言功能,也是廣大開發者翹首以盼的功能,曾在語言問題的反饋中高居 第二位。有了這一功能,開發者就能夠創建函數類型的別名,但不能創建其他任何類型。

利用類型別名您可以為任何現有的類型創建新的名稱,然后將新創建的名稱用在原始類型可以出現的任何地方。創建新名稱並不會真的定義一個新類型,只不過是引入一個簡短的別名而已。該別名甚至能通過類型等同測試:

typedef Integer = int;

void main() {
  print(int == Integer); // true
}

那么,類型別名可以怎么用?一種常見的用法是給某類型指定一個更短或更具描述性的名稱,以便您的代碼更易於理解和維護。

比如,給 JSON 類型指定別名就是種不錯的用法 (此示例由 GitHub 用戶 Levi-Lesches 提供,特此感謝)。在下列示例中,我們可以定義一個新的類型別名 Json,它將一個 JSON 文檔描述為一個 map,其鍵為 String,值為任意值 (使用動態類型)。這樣,當我們定義名為 fromJson 的構造函數和 json get函數時,就能使用該 Json 類型別名。

typedef Json = Map<String, dynamic>;

class User {
  final String name;
  final int age;

  User.fromJson(Json json) : 
    name = json['name'],
    age = json['age'];

  Json get json => {
    'name': name, 
    'age': age,
  };
}

您也可以對指代某個類的類型別名調用構造函數,比如以下示例就非常合規:

main() {
  var j = Json();
  j['name'] = 'Michael';
}

通過使用類型別名來指代復雜類型,可以讓讀者更容易理解您代碼的不變量。例如,以下代碼定義了一個類型別名來描述鍵值為泛型類型 X、值為類型 List<X> 的映射。如果給該類型指定一個具有單一類型參數的名稱,映射的常規結構在代碼讀者眼中會變得更為清晰。

typedef MapToList<X> = Map<X, List<X>>;
void main() {
  MapToList<int> m = {};
  m[7] = [7]; // OK
  m[8] = [2, 2, 2]; // OK
  for (var x in m.keys) {
    print('$x --> ${m[x]}');
  }
}

=>

7 --> [7]
8 --> [2, 2, 2]

如果您嘗試使用不匹配的類型,將出現分析錯誤:

m[42] = ['The', 'meaning', 'of', 'life']; 


=>

The element type 'String' can't be assigned to the list type 'int'.

您甚至可以使用類型別名來重命名公共庫中的類。假設現在公共庫中有一個 PoorlyNamedClass 類,您想要將它重命名為 BetterNamedClass。如果您只是重命名該類,那么您的 API 客戶那邊將會出現突發編譯錯誤。而使用類型別名,則不會出現這一問題,您可以隨意重命名,只不過要先為舊的類名稱定義一個新的類型別名,再給舊名稱添加幾行 @Deprecated 注解。這樣,使用 PoorlyNamedClass 的代碼雖然會出現警告,但仍可繼續編譯並照舊正常運行,讓用戶有時間升級其代碼。

mylibrary.dart:

class BetterNamedClass {}

@Deprecated('Use BetterNamedClass instead')
typedef PoorlyNamedClass = BetterNamedClass;

main.dart


import 'mylibrary.dart';


void main() {
  PoorlyNamedClass p;
}

=>

'PoorlyNamedClass' is deprecated and shouldn't be used. Use BetterNamedClass instead.

下面介紹實現 BetterNamedClass 和棄用 PoorlyNamedClass 的方法 (在一個名為 mylibrary.dart 的文件中)。

class BetterNamedClass {...}

@Deprecated('Use BetterNamedClass instead')
typedef PoorlyNamedClass = BetterNamedClass;

下面是嘗試使用 PoorlyNamedClass 時會發生的情況:

import 'mylibrary.dart';
void main() {
 PoorlyNamedClass p;
}
=>
'PoorlyNamedClass' is deprecated and shouldn't be used. Use BetterNamedClass instead.

類型別名功能從 Dart 2.13 版開始即可使用,要啟用此功能,需要將您 pubspec 中版本較低的 Dart SDK 約束設置為最低 2.13 版,如下所示:

environment:
  sdk: ">=2.13.0 <3.0.0"

此功能支持向后兼容,這要歸功於 語言的版本管理。也就是說,SDK 約束版本低於 2.13 的 package 可以安全地引用 2.13 版 package 中定義的類型別名,盡管 2.13 版之前的 package 不能定義其自己的類型別名。

Dart 2.13 FFI 的變化

我們還在 Dart FFI (這是用來調用 C 語言代碼的互操作機制) 中引入了一些新功能。

首先,FFI 現在支持包含內聯數組 (#35763) 的結構。假設某 C 語言結構具有如下內聯數組:

struct MyStruct {
  uint8_t arr[8];
}

現在,只需將包含一個類型實參的元素類型指定給 Array,即可直接將該結構體封裝在 Dart 中,如下所示:

class StructInlineArray extends Struct {
  @Array(8)
  external Array<Uint8> arr;
}

其次,FFI 現在支持封裝結構體 (#38158)。結構體通常都被放置在內存中,以便其位於地址邊界內的成員能夠被 CPU 更輕松地存取。使用 封裝結構體 時,為了減少整體內存占用量,經常會以平台特有的方式忽略一些填充字節。借助新的 @Packed(<alignment>) 注解,您可以輕松指定填充字節。例如,下列代碼創建的結構體就指定其在內存中時的字節對齊為 4。

@Packed(4)
class TASKDIALOGCONFIG extends Struct {
  @Uint32()
  external int cbSize;
  @IntPtr()
  external int hwndParent;
  @IntPtr()
  external int hInstance;
  @Uint32()
  external int dwFlags;
…
}

Dart 2.13 在性能方面的提升

我們一直在不斷努力降低 Dart 代碼的應用體量和內存占用量。在大型 Flutter 應用中,經過 AOT 編譯 Dart 程序的元數據的內部結構可能要占用非常可觀的內存。這些元數據的存在大多是為了實現熱重載、交互式調試,以及格式化可讀堆棧軌跡等功能,這些功能在需要部署的應用中從不會用到。過去幾年來,我們一直在重構 Dart 原生運行時環境,以便盡可能多地消除這種開銷。其中一些改進適用於所有以版本模式構建的 Flutter 應用,而有些則需要使用 --split-debug-info 標志將 AOT 編譯應用中的調試信息拆分出來,從而放棄可讀的堆棧軌跡。

Dart 2.13 在內存消耗上取得了很大的進步,在使用 --split-debug-info 時,程序元數據占用的空間量降幅顯著。例如,Flutter Gallery 的空間占用降幅達到 30%: 在 --split-debug-info 模式下,程序元數據在 Dart 2.12 中要占用 5.7Mb,而在 Dart 2.13 中僅需 3.7Mb。以 Flutter Gallery 應用為例,在 Android 平台上,包含調試信息的發布 APK 大小為 112.4MB,不包含的情況下大小為 106.7MB (總體積減少了 5%)。該 APK 中包含了大量的資源。僅從 APK 內部的元數據體積來說,從 Dart 2.12 平台上的 5.7MB 減少至 Dart 2.13 平台上的 3.7MB (減少了35%!)。

如果對您來說應用體量和內存占用量比較重要,可以使用 --split-debug-info 標志省略調試信息。請注意,一旦這么做,您需要使用 symbolize 命令 來重新使堆棧軌跡可讀。

Dart 官方 Docker 鏡像發布以及 Cloud 支持

Dart 現在在 官方鏡像 中可用,雖然 Dart 早已提供了 Docker 鏡像,但為了遵循最佳實踐,這些 新的 Dart 鏡像 是由 Docker 進行測試和驗證的。它們還支持 AOT 編譯,可以大大減少構建容器的大小,並且可以在容器環境中提升部署速度——如 Cloud Run

雖然 Dart 始終專注於使 Flutter 等應用框架在每個屏幕上構建出色的界面,但我們意識到,大多數用戶體驗的背后至少有一個托管服務。通過讓 Dart 輕松構建后端服務來支持全棧體驗,開發者可以使用與前端 widget 相同的語言和業務邏輯,將他們的應用擴展到雲端。

通常來說,將 Dart 用於 Flutter 應用程序的后端,特別符合 Google 無服務器管理平台 Cloud Run 的簡單性和可擴展性。這也包括零擴展,意味着當您的后端不處理任何請求時,就不會產生成本。我們與 Google Cloud 團隊合作,提供 Dart 的函數框架,這是一個 packages、工具和實例的集合,使開發者們能夠輕松地編寫 Dart 函數,以取代處理 HTTP 請求和 CloudEvents 的完整服務器部署。

您可以查看我們的 Google Cloud 官方文檔 以便開始使用。

后續更新預告

在接下來的版本中,還會有一些令人激動的改變。和以往一樣,您可以使用 language funnel 追蹤器留意我們的后續工作。

我們一直努力改進的一個方面是,為 Dart 和 Flutter 定義一組新的 canonical lint。lint 是配置 Dart 靜態分析 的一種高效方式,但由於可能有成百上千個 lint 要啟用或禁用,有時可能會難以抉擇。眼下,我們正打算定義兩組要在 Dart 和 Flutter 項目中默認應用的 canonical lint。預計這兩組 lint 將在下一個穩定版中默認啟用。如果您想要提前預覽,請查看 lintsflutter_lints 這兩個 package。

最后,如果您深度嵌套了 Dart VM 運行時環境,請注意,我們打算棄用其現有的機制。我們將用一個基於 Dart FFI 的更快、更靈活的模型取代它 (請參閱追蹤問題 #45451)。

Dart 2.13 版現已發布

Dart 2.13 版現已在 Dart 2.13Flutter 2.2 SDK 中推出,此版本新增了類型別名功能,還改進了 FFI。

如果您一直在等待將自己的依賴項遷移到空安全環境的時機,不妨使用 dart pub outdated 再次檢查一下。目前,前 500 個最受歡迎的 package 中,93% 的 package 都已遷移,現在沒准就是您遷移的好時機。在此,謹向那些已經遷移的開發者致以最衷心的感謝!

歡迎試用本指南中介紹的新功能和改進后的功能,並將您使用后的感想告訴我們。請在下方評論區留言。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM