0%

【文章翻譯】Announcing Dart 2.7: A safer, more expressive Dart

【文章內容使用 Gemini 1.5 Pro 自動翻譯產生】

宣佈 Dart 2.7:更安全、更具表現力的 Dart

今天,我們宣布 Dart 2.7 SDK 的穩定版本發布,它為開發人員提供了額外的功能。對於 Dart(我們針對任何平台上的快速應用程式進行客戶端優化的語言)來說,這是忙碌的一年。我們發布了六個新版本,其中包含數十項新功能。看到 Dart 社群使用這些功能是非常有益的,我們很高興最近的 GitHub Octoverse 報告將 Dart 列為 增長最快的語言(按貢獻者人數排名)。

Dart 2.7 新增了對擴展方法的支援,以及一個用於處理包含特殊字元的字串的新套件。我們對空安全(類型安全的可空和不可空類型)進行了更新,並在 DartPad 中提供了一個全新的空安全遊樂場體驗。在生態系統層級,pub.dev 有一個新的「喜歡」功能,用於向您喜歡的套件提供回饋。Dart 2.7 現在可以從 dart.dev 以 SDK 下載方式取得,它也內建於今天的 Flutter 1.12 版本 中。

擴展方法

Dart 2.7 新增了一個期待已久、功能強大的新語言特性:擴展方法。這些方法使您能夠向任何類型(即使是您無法控制的類型)添加新功能,並具有常規方法調用的簡潔性和自動完成體驗。

讓我們看一個小例子:新增對從字串解析整數和雙精度浮點數的支援。作為應用程式開發人員,我們無法更改 String 類別,因為它是在 dart:core 函式庫中定義的,但使用擴展方法,我們可以擴展它!定義此擴展後,我們可以在 String 上調用新的 parseInt 方法,就像該方法是在 String 類別本身上定義的一樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
extension ParseNumbers on String {
int parseInt() {
return int.parse(this);
}

double parseDouble() {
return double.parse(this);
}
}

main() {
int i = '42'.parseInt();
print(i);
}

擴展方法是靜態的

擴展方法是靜態解析和分派的,這表示您無法在類型為 dynamic 的值上調用它們。這裡的調用會在執行階段拋出異常:

1
2
3
4
dynamic d = '2';
d.parseInt();

→ Runtime exception: NoSuchMethodError

擴展方法與 Dart 的 類型推斷 配合良好,因此在以下變數 v 中,推斷類型為 String,並且 String 上的擴展可用:

1
2
var v = '1';
v.parseInt(); // Works!

因為擴展方法是靜態解析的,所以它們的速度與調用靜態函數或輔助方法一樣快,但調用語法更友好。

擴展可以具有類型變數

假設我們想要在 List 上定義一個擴展,用於獲取偶數索引處的元素。我們希望這個擴展適用於任何類型的列表,返回與輸入列表相同類型的新列表。我們可以透過使擴展泛型並將其類型參數應用於它擴展的類型和擴展方法來做到這一點:

1
2
3
4
5
extension FancyList<T> on List<T> {
List<T> get evenElements {
return <T>[for (int i = 0; i < this.length; i += 2) this[i]];
}
}

擴展方法實際上是擴展成員

我們將此功能稱為「擴展方法」,因為如果您在其他程式語言中使用過相應的語言功能,那麼這是熟悉的術語。但在 Dart 中,此功能更通用:它還支援使用新的 getter、setter 和運算子來擴展類別。在上面的 FancyList 範例中,evenElements 是一個 getter。以下是一個新增用於移位字串的運算子的範例:

1
2
3
4
5
extension ShiftString on String {
String operator <<(int shift) {
return this.substring(shift, this.length) + this.substring(0, shift);
}
}

社群中的絕佳範例

我們已經看到 Dart 社群中的許多開發人員試驗擴展方法。以下是一些我們目前看到的酷炫用法。

Jeremiah Ogbomo 建立了 time 套件,它在 num(整數和雙精度浮點數的基類)上使用擴展,以便輕鬆建立 Duration 物件:

1
2
3
4
5
6
7
8
// 透過 num 上的 `minutes` 擴展建立 Duration。
Duration tenMinutes = 10.minutes;

// 透過 num 上的 `hours` 擴展建立 Duration。
Duration oneHourThirtyMinutes = 1.5.hours;

// 使用 DateTime 上的 `+` 運算子擴展建立 DateTime。
final DateTime afterTenMinutes = DateTime.now() + 10.minutes;

Marcelo Glasberg 建立了一個 i18n(國際化)套件,它使用擴展方法來簡化字串本地化:

1
Text('Hello'.i18n) // 以英文顯示 Hello,以西班牙文顯示 Hola,依此類推。

Simon Leier 建立了 dartx 套件,其中包含許多核心 Dart 類型的擴展。一些範例:

1
2
3
4
5
var allButFirstAndLast = list.slice(1, -2);    // [1, 2, 3, 4]
var notBlank = ' .'.isBlank; // false
var file = File('some/path/testFile.dart');
print(file.name); // testFile.dart
print(file.nameWithoutExtension); // testFile

Brian Egan 正在使用擴展方法 更新 熱門的 RxDart 套件,以重新定義用於處理串流的 API。

安全的子字串處理

Dart 的標準 String 類別使用 UTF-16 編碼。這是程式語言中的常見選擇,尤其是那些同時提供在設備上原生運行和在 Web 上運行的支援的語言。

UTF-16 字串通常運作良好,並且編碼對開發人員是透明的。但是,在操作字串時,尤其是在操作使用者輸入的字串時,您可能會遇到使用者認為的字元與 UTF-16 中編碼為程式碼單位的字元之間的差異。讓我們看一個小例子,提取使用者輸入的字串的前三個字元:

1
2
3
4
5
var input = ['Resume'];
input.forEach((s) => print(s.substring(0, 3)));

$ dart main.dart
Res

到目前為止沒有問題;我們列印了輸入列表中字串的前三個字元,結果是 Res。現在讓我們考慮來自不同地區的使用者,他們可能會輸入包含重音符號、諺文(韓文)甚至表情符號組合的字串來表示「簡歷」的概念:

1
2
3
4
5
6
7
8
9
// 新的更長的輸入列表:
var input = ['Resume', 'Résumé', '이력서', '💼📃', 'Currículo'];

$ dart main.dart
Res
Ré
이력서
💼�
Cur

嗯,其中一些有效,但 Résumé 和 💼📃 元素發生了什麼事?對於 Résumé,為什麼我們得到一個「兩個字元」的字串?對於 💼📃,奇怪的問號是什麼意思?這裡的問題在於 Unicode 的黑暗角落。Résumé 中的重音 é 實際上是兩個程式碼點:一個 e 和一個 組合尖音符。而 📃,捲曲頁面 表情符號,是一個單獨的程式碼點,碰巧使用 U+d83d U+dcc3 的代理對進行編碼。困惑了嗎?

正如我們所說,您通常不需要擔心字元和程式碼點。如果您所做的只是接收、傳遞和處理整個字串,則內部編碼是透明的。但是,如果您需要迭代字串中的字元或操作字串的內容,則可能會遇到問題。好消息是 Dart 2.7 引入了一個新套件 characters 來處理這些情況。此套件支援將字串視為使用者感知字元的序列,也稱為 Unicode 字素叢集。使用 characters 套件,我們可以透過對縮短文字的程式碼進行少量更改來修復我們的程式碼:

1
2
3
4
5
// 之前:
input.forEach((s) => print(s.substring(0, 3)));

// 之後,使用 characters 套件:
input.forEach((s) => print(s.characters.take(3)));

首先,我們從 s 中的字串建立一個新的 Characters 實例(使用方便的 .characters 擴展方法)。然後,我們使用巧妙的 take() 方法來提取前 3 個字元。

此新套件的技術預覽版 可在 pub.dev 上取得。我們很樂意聽到您對此套件的想法。如果您發現任何問題,請 回報

空安全預覽

幾個月前,我們 宣布 了我們在 Dart 中支援空安全的意圖,新增了安全存取物件參考而不會觸發空參考異常的支援。今天,我們將為您提供預覽空安全靜態分析的方法。讓我們看一個小的激勵範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void main() {
Person('Larry', birthday: DateTime(1973, 03, 26)).describe();
Person('Sergey').describe();
}

class Person {
String firstName;
DateTime birthday;
Person(this.firstName, {this.birthday});

void describe() {
print(firstName);
int birthyear = birthday?.year;
print('Born ${DateTime.now().year - birthyear} years ago');
}
}

如果我們執行此程式碼,它會在描述第二個人時因空指標異常而崩潰,因為該個人沒有設定生日。我們犯了一個編碼錯誤:雖然我們確實預料到有些人透過在建構函數中將 birthday 欄位設為可選,並透過在 birthday?.year 中測試空生日來處理生日未知的情況,但我們忘記了處理 birthyear 為空的情況。

讓我們嘗試將此程式碼貼上到我們新的 空安全遊樂場 中,這是 DartPad 的一個特殊版本,其中包含空安全功能的靜態分析部分的技術預覽。即使不執行程式碼,我們也可以看到三個問題:

DartPad with null safety showing three analysis errors related to nulls

透過修復這些分析錯誤,我們可以開始利用空安全。嘗試在空安全遊樂場中進行以下編輯(最終得到 此安全程式碼):

  1. 若要宣告 birthday 可能為空,請將 DateTime birthday 更改為 DateTime? birthday
  2. 若要宣告當 birthday 為空時 birthyear 可能為空,請將 int birthyear 更改為 int? birthyear
  3. 將最後一個列印調用包裝在空測試中: if (birthyear != null) {…}

我們希望此範例能讓您很好地了解我們希望使用空安全的體驗。如前所述,這個遊樂場只是空安全部分的早期技術預覽,因為它正在構建中。我們正在努力完成 Dart SDK 中空安全的第一個測試版本。以下是我們正在為測試版本進行的工作:

  1. 完成可空和不可空參考的完整實作
  2. 將空安全整合到 Dart 的類型推斷和智慧提升中(例如,在賦值或空檢查後允許安全存取可空變數)
  3. 移植 Dart 核心函式庫 以宣告哪些類型是可空的,哪些類型是不可空的
  4. 加入一個遷移工具,它可以自動執行大多數移植 Dart 應用程式和套件的升級任務

一旦這項工作完成,我們將在測試版 SDK 中提供它,您可以開始在您的應用程式和套件中利用此功能。我們還計劃隨著新功能的實作而持續更新空安全遊樂場。

雖然我們確信許多開發人員會希望在空安全可用後立即使用它,但您可以在方便時進行遷移,在您準備好時選擇加入此功能。尚未選擇加入此功能的函式庫和套件將能夠依賴 已選擇加入 的函式庫,反之亦然。

在接下來的幾個月中,我們將會更多地討論空安全,包括關於如何為轉換做準備的更詳細建議。

在 pub.dev 上喜歡 👍 套件

今天在 pub.dev 上也推出了一個套件的全新喜歡功能。這引入了一個新的「人為信號」來表示您喜歡的套件。若要喜歡一個套件,只需點擊套件詳細資訊旁邊的豎起大拇指圖示:

pub.dev package detail page with new Like-feature voting button

目前,我們並未將喜歡次數納入我們的整體評分模型中,但我們計劃在以後的版本中這樣做。我們還計劃對我們的一般搜尋使用者介面和列表頁面進行視覺上的修改,以突顯套件的受歡迎程度。

謝謝

代表 Dart 團隊,我們要感謝您以及 Dart 社群中的每個人一直以來的支援!請繼續向我們提供回饋,並參與 Dart 討論和社群。沒有 Dart 社群的支援,我們將無法成為一個運作良好的開源專案。

2019 年對於 Dart 來說是令人難以置信的激動人心的一年,但我們並不止步於此。我們對 2020 年有著大膽的計劃,包括發布諸如 dart:ffi空安全 之類功能的穩定版本,以及引入新功能。我們邀請您立即開始使用 Dart 2.7。它可以從 dart.dev 取得,在今天的 Flutter 1.12 版本中,以及在最近 重新設計的 DartPad 上取得。


宣佈 Dart 2.7:更安全、更具表現力的 Dart 最初發佈在 Medium 上的 Dart 中,人們在那裡透過突出顯示和回應這個故事來繼續對話。