0%

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

Dart 實習生生活

我不知道。

這三個字每天都在軟體工程師的腦海中浮現。工程工作由謎題和問題組成,所以不知道某件事只是另一個指標,表明工程師有成長的能力。然而,缺乏知識也會產生自我懷疑。

在今年秋季開始實習之前,我對程式語言、類型系統,甚至變異數(這是我整個專案)都不太了解。據我所知,變異數只是一個我需要計算平方根的值,這樣我才能得到標準差。程式語言和編譯器似乎是一種讓大多數開發人員感到神秘的黑魔法。

值得慶幸的是,我的經理是一位讀心者,他理解我的感受,並消除了我的緊張情緒。

_”所以,我要坦誠相待,這個專案會讓你完全陷入困境。你還什麼都不知道,但這沒關係,因為我希望你提出問題,很多問題,”_他解釋說。

直接進入一個我幾乎沒有任何背景知識的新專案是很可怕的,但我知道我得到了經理的支援。他給了我_嘗試_的自由,_犯錯_的自由。這對我來說意義重大。

與經理的快速交談開啟了我第一個月的工作:我在西雅圖弗里蒙特見到了我的團隊,並準備將自己投入到這個叫做_變異數_的深不可測的未知領域,您可以在Dart 宣告端變異數一文中閱讀相關內容。

我每週都會和經理一起參加變異數訓練營,他會提供我必須轉化為程式碼的理論。我記得我讀過我將要實作的變異數功能的提案,並對完成該專案所需的七個里程碑感到不知所措:(1) 掃描然後解析新的關鍵字,(2) 修改 kernel AST,(3) 在子類型演算法中支援變異數,(4) 加入錯誤和警告,(5) 變更上下限計算,(6) 變更類型推斷規則,以及 (7) 處理執行階段行為變更。

在變更 kernel(編譯器的中間表示)和子類型邏輯之間,我需要序列化和反序列化變異數註釋。執行階段行為被認為是一個延伸目標,但我幾乎無法理解它之前的六個任務。我不確定從哪裡開始或如何開始。

但情況好轉了。

我每天早上 9 點開始工作。炒雞蛋、香腸、羽衣甘藍和柳橙汁就是我所需要的。我準備好眯著眼睛看程式碼,想知道人們究竟是如何製作第一個編譯器的。

我經常同時處理不同的組件。這讓我保持忙碌,而且我喜歡組織目標。每天早上我都會建立一個新文件,列出我想完成的任務,將目標分解成可管理的任務。

A to-do list with most items crossed out (done!).
典型的待辦事項清單

到了晚上,丹麥奧胡斯的 Google 辦公室就開始活躍起來。我知道我必須與幾位來自丹麥的工程師合作,所以我經常寫下我的問題,最後完成並在西雅圖深夜發送出去。程式碼審查時間提前了 9 個小時,但幸運的是,時間差也讓我可以在程式碼被批評之前更頻繁地校對自己的工作。

團隊的批評提高了我判斷自己程式碼的能力,並思考不同實作方式的權衡。我的審查者指出了我忽略的地方,並詢問了關於變異數是什麼以及如何使用它的問題。我會修復我的程式碼,回答他們的問題,並確保在未來注意相同的問題。更重要的是,我會深入挖掘並問自己,他們是如何想到這個改進的?除了程式碼的修正版本之外,我可以從中學到什麼知識? 對我來說,重要的是我從每條評論中學習,而不是漫不經心地點擊方便的 完成 按鈕。

我的學習方式是透過提問。作為一個固執的軟體工程師,在咬牙切齒地研讀那些毫無意義的程式碼和勉強地找程式碼庫中的專家幫忙之間,總是一種微妙的平衡。後者幾乎總是能節省更多時間。我更喜歡分配相當多的時間獨立工作,在遇到新領域時做筆記並寫下問題,然後當我有 2 或 3 個問題和一個堅實的障礙時,再去詢問其他工程師。我意識到,與單獨花 3 個小時排除故障相比,詢問那些需要花費 3 個小時排除故障的問題,與其他人花 10 分鐘指導和解釋相比,我的效率要高得多。

工作場所的安全也會提高生產力。

對我來說,安全是以提問、開玩笑和與團隊共進午餐的形式出現的。當時鐘閃爍到 11:30 時,我知道接下來的半小時將充滿溫暖的食物和明亮的面孔。每天我都會聽到最精心設計的故事和想法:擁有 6 根手指的好處、收到一隻煮熟的龍蝦作為聖誕禮物、在 Factorio 中混合兩根不同的管道後數小時的清理工作……任何事情。我的團隊在我犯錯和取得成就的過程中都支援我。一個好的團隊會讓早上起床去上班變得容易得多。

午餐前後的時間也同樣愉快。我大部分時間都在閱讀程式碼和理解新概念。Dart 編譯器由許多大型的、不斷變化的組件組成,這意味著我需要深入研究幾個不同的程式碼庫。我喜歡有機會與負責靜態分析的前端團隊以及管理執行階段行為的後端團隊聯繫。看到這些組件如何協同工作以在 Dart 語言中建立新功能,真是太棒了。

實作和理論之間的裂痕是我專案中最具挑戰性的方面。並不是每天都需要將證明變成有形的程式碼。在處理變異數時,當我有問題時,這種裂痕就會浮出水面——有時我的詢問會將語言提案的組件與適當的實作交織在一起。我尋求答案的人要么知道哪個檔案包含子類型演算法,要么知道子類型變更如何影響賦值運算,但從來不知道兩者兼而有之。這感覺就像用幾塊膠合板和藝術家對最終結果的渲染來建造樹屋。

儘管如此,在 Dart 上工作仍然是非常有益和有趣的。我喜歡挑戰,尤其是知道除了變異數之外還有很多領域需要學習。Dart 過去是,現在仍然是,不斷發展以更好地支援使用該語言的開發人員。當出現關鍵問題並需要立即關注時,我看到團隊像調校良好的時鐘一樣運作。我傾聽團隊回顧以前的錯誤,確保未來的流程不再重複這些錯誤。

我在 Dart 團隊的實習提高了我的工程技能、溝通能力以及對自身能力的信心。它教會了我擁有一個友好但功能齊全的團隊的重要性。它教會我理論轉化為程式碼的許多方法,以及這樣做的必要解決問題的技巧。它教會我更多關於我自己的知識,以及我在工作中所珍視的東西。

感謝 Dart。下次見。


Dart 實習生生活 最初發佈在 Dart 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

【文章內容使用 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 中,人們在那裡透過突出顯示和回應這個故事來繼續對話。

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

全新的 DartPad.dev,支援 Flutter

今天,我們宣布對 DartPad.dev 進行重大升級,它採用了全新的外觀和體驗,並支援熱門的 Flutter UI 工具包。DartPad 是我們的線上編輯器,可以直接在您的瀏覽器中運行 Dart 程式,現在它也可以運行 Flutter 應用程式了。

立即開始創作

要建立新的 Flutter 專案,請點擊「新增面板」按鈕,然後選擇 Flutter

這會在編輯器中加入一些起始程式碼並運行它。每當 DartPad 偵測到您正在使用 package:flutter 時,就會出現一個面板來顯示 UI。或者使用「範例」選單來尋找 Flutter 範例。

功能

Flutter 的所有核心函式庫,例如 cupertinomaterial,都可以使用,DartPad 可以顯示它們的文件。選擇一個符號即可在底部面板中查看文件。

DartPad 使用 Dart 格式化工具 (dartfmt) 格式化程式碼、提供輔助、提供修正、建議自動完成,並顯示錯誤和警告。

當您準備好分享您的程式碼片段時,您可以 建立 GitHub gist,並將 gist ID 放入像這樣的 URL 中:https://dartpad.dev/<GistID>。您可以在錯誤報告、StackOverflow 問題或您選擇的社群媒體平台上分享此連結。查看分享指南以了解更多詳細資訊。

您也可以將 DartPad 嵌入到頁面中,例如在此程式碼實驗室中。嵌入式 DartPad 特別適用於文章、程式碼實驗室和教學。(如果您有興趣在程式碼實驗室和教學中使用 DartPad,請查看本指南)。要了解更多關於將 DartPad 加到頁面的資訊,請查看嵌入指南

感謝!

DartPad 是 Dart 開源專案 的一部分。如果您提交了 DartPad 的 問題 或變更,感謝您使其變得更好。如果您想貢獻,請從 github.com/dart-lang/dart-pad 開始。要了解更多關於在 Web 上運行 Flutter 的資訊,請造訪 flutter.dev/web

我們迫不及待地想看看您會創造出什麼!


全新的 DartPad.dev,支援 Flutter 最初發佈在 Medium 的 Dart 上,人們在那裡透過醒目顯示和回應這個故事來繼續對話。

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

宣佈推出 Dart 2.6 和 dart2native:將 Dart 編譯成獨立的原生執行檔

Dart 已經為行動裝置和網路提供了 廣泛的編譯器集,用於建構經過生產最佳化的程式碼。這些靈活的編譯器使我們的框架合作夥伴能夠鎖定各種形式:在 Android 和 iOS 上的 Flutter 應用程式、在 網路桌面 上的 Flutter 應用程式、在 網路 上的 AngularDart 應用程式,以及在 嵌入式裝置 上的 Google 助理。

今天,我們宣佈推出 dart2native,它是我們現有編譯器集的擴展,能夠將 Dart 程式編譯成包含預先編譯機器碼的獨立執行檔。使用 dart2native,您可以在 macOS、Windows 或 Linux 上使用 Dart 建立命令列工具。此功能的宣佈圖片 是用此功能本身實現的 :-)

Dart Native 和 dart2native 編譯器

Dart 多年來一直支援 AOT(提前)編譯成原生機器碼,因此 Dart Native 是一項相當成熟的技術。然而,過去我們只透過 Flutter 在 iOS 和 Android 行動裝置上公開此功能。

使用 dart2native,我們正在擴展我們的原生編譯支援,以支援運行 macOS、Windows 和 Linux 的傳統桌面作業系統。由於使用 dart2native 建立的執行檔是獨立的,因此它們可以在未安裝 Dart SDK 的機器上運行。而且由於它們是使用 Dart 的 AOT 編譯器編譯的,因此執行檔只需幾毫秒即可開始運行。與其他 Dart 編譯器和運行時一樣,在編譯成原生程式碼時,Dart 中也提供了同一組豐富且一致的 核心函式庫

我們聽到了許多客戶要求桌面作業系統的 AOT 編譯——這是我們議題追蹤器中 評分第六高的議題——因此我們很高興能夠提供此功能。

如果您以前使用過 dart2aot,那麼從 2.6 開始,您將使用 dart2native。它提供了 dart2aot 功能的超集。

使用 dart2native 建構命令列應用程式

dart2native 編譯器是建構和部署 基於 Dart 的命令列應用程式 的絕佳選擇。這些應用程式通常使用諸如 dart:io(基本 I/O)、package:http(網路)和 package:args(參數解析)之類的函式庫。讓我們回顧一下將「hello, world」應用程式編譯成執行檔的基礎知識:

原始碼 hello.dart:

1
2
3
main() {
print('Hello Dart developers');
}

將 hello.dart 編譯成 hello 執行檔:

1
2
$ dart2native src/hello.dart -o hello
Generated: /Users/mit/hello

運行 hello 並測量執行時間:

1
2
3
4
5
6
$ time ./hello
Hello Dart developers

real 0m0.049s
user 0m0.018s
sys 0m0.020s

請注意命令如何啟動、列印到 stdout 並在僅 49 毫秒的組合時間內退出!

我們已經看到一些 Dart 開發人員嘗試使用 dart2native 開發命令列工具:

  • 來自 SASS(一種流行的 CSS 擴展工具)團隊的 Natalie 報告說,在將他們基於 Dart 的 SASS 實作切換到使用 dart2native 編譯後,它的效能現在可以與基於 C++ 的實作 LibSass 相媲美。
  • 來自 Dart DevRel 團隊的 Filip 使用 dart2native 重新編譯了他的 linkchecker 工具,並在檢查小型網站時發現 速度提高了 27 倍

透過 dart:ffi 與 C 程式碼互通

原生應用程式通常需要從周圍的作業系統存取原生功能。這些系統 API 通常在基於 C 的原生函式庫中公開,而 Dart 透過 dart:ffi 支援與這些函式庫的互通性,dart:ffi 是我們用於 C 互通 的新機制,我們在 Dart 2.5 中以預覽版推出。dart2native 編譯器與 dart:ffi 相容,因此您可以建立和編譯使用它的原生 Dart 應用程式。

一位團隊成員最近使用 dart:ffi 建立了一個用於控制台應用程式開發的 dart_console 函式庫,它具有獲取視窗尺寸、讀取和設定游標位置、管理顏色以及讀取鍵和控制序列等功能。使用 dart:ffi 的能力使 Dart 成為一種非常強大的控制台應用程式語言。

kilo:用不到 500 行 Dart 程式碼編寫的 7MB 程式碼編輯器

使用 Dart 核心函式庫、dart:ffi 和 dart_console 函式庫,我們可以建立非常有趣的控制台應用程式。dart_console 套件包含一個完整的 kilo 示範,這是一個僅用約 500 行 Dart 程式碼 編寫的控制台文字編輯器。kilo 這個名稱來自它的起源,kilo.c,它是一個用大約 1000 行 C 程式碼編寫的實作。

使用新的 dart2native 編譯器,我們可以輕鬆地打包它,最終得到一個 7MB 的獨立程式碼編輯器。以下是一個編譯編輯器的示範,然後使用編譯後的編輯器來編輯其自身的原始程式碼以修復錯誤:

用 Dart 編寫並使用 dart2native 編譯成執行檔的 kilo 編輯器正在編輯它自己的原始程式碼

使用 dart2native 建構服務

dart2native 編譯器的另一個潛在用途是小型服務——例如,支援使用 Flutter 編寫的前端應用程式的後端。近年來,一種日益增長的趨勢是使用在 無伺服器計算 上運行的服務。這些是完全託管的服務,可以自動擴展,包括從零擴展到零(不運行),由於它們僅在實際運行時才計費,因此有可能大大降低成本。Google Cloud 透過 Cloud Run 提供無伺服器計算。

對於無伺服器後端,服務快速啟動至關重要。傳統上,基於 Dart 的服務使用我們的 JIT(即時)編譯器運行,但 JIT 基礎的執行在啟動時延遲很高,因為程式碼需要先編譯和預熱才能開始執行。透過將服務的程式碼預先編譯為原生程式碼,您可以避免這種延遲並立即開始運行。此外,使用原生程式碼,您可以建立磁碟佔用空間小且獨立的 Dart 服務,從而大大減少 Dart 服務運行的容器的大小。Dart 開發人員 Paul Mundt 最近 記錄了他使用 dart2native 編譯器的經驗;他能夠將 Docker 映象的大小減少 91%,從使用 JIT 編譯程式碼的 220MB 減少到使用原生程式碼的僅 20MB!有關 伺服器端應用程式套件 的更多詳細資訊,請參閱我們的文件。

可用性

dart2native 編譯器從 2.6 版開始在 Dart SDK 中提供,今天開始可以從 dart.dev/get-dart 獲取。安裝 SDK 後,您應該會在 bin/ 目錄和 PATH 中看到新的編譯器。Dart.dev 上有 更多文件

如果您透過 Flutter 獲取 Dart SDK,請注意目前的 Flutter 版本對 dart2native 的支援不完整。我們建議您從 dart.dev/get-dart 安裝 Dart 2.6 SDK。

已知限制

此初始版本的 dart2native 編譯器有一些已知的限制,如下所示。您可以透過在我們的 GitHub 議題追蹤器中為議題添加「豎起大拇指」來讓我們知道哪些議題對您很重要。

  • 不支援交叉編譯(議題 28617):dart2native 編譯器僅支援為其運行的作業系統建立機器碼。因此,如果您想為所有三個作業系統建立執行檔,則需要在 macOS、Windows 和 Linux 上運行三次編譯器。執行此操作的一種方法是使用支援所有三個作業系統的 CI(持續整合)提供程式。
  • 不支援簽名(議題 39106):生成的執行檔使用與標準簽名工具(例如 codesign 和 signtool)不相容的格式。
  • 不支援 dart:mirrors 和 dart:developer(請參閱 Dart 核心函式庫)。

Dart 2.6 中的其他變化

Dart SDK 2.6 版還有一些其他變化。

我們在 Dart 2.5 中推出了 dart:ffi 的預覽版,這是我們用於 C 互通 的新機制。Dart 2.6 有一個新版本的 dart:ffi。這個新版本有許多重大 API 變更,以使我們的 API 更易於使用、提供更多類型安全,並提供方便的記憶體存取。有關其他詳細資訊,請參閱 Dart 2.6 變更日誌。透過這些變更,dart:ffi 進入測試階段,我們預計 API 變更的頻率會大大降低,並且預計總體穩定性會很高。請繼續透過 議題追蹤器 向我們提供回饋。

Dart 2.6 還包含一個令人興奮的新語言功能的預覽版,擴展方法。我們還有一些潤色和工具工作要完成此功能,但我們希望在下一個 Dart SDK 版本中正式推出它。到時候我們將會更多地談論擴展方法;現在,您可以閱讀有關 此功能背後的設計考量

後續步驟

下載 Dart 2.6 SDK(dart.dev/get-dart),使用 dart2native 建構一些酷炫的東西,然後告訴我們。如果您願意分享詳細資訊,請在此文章底部留言。我們很期待看到您建構的東西!


宣佈推出 Dart 2.6 和 dart2native:將 Dart 編譯成獨立的原生執行檔 最初發佈在 Dart 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

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

Dart 擴展方法基礎

在未來的版本中,Dart 語言將添加一個新功能,擴展方法,它允許您(假裝)向現有類型添加新成員。即使它實際上只是一個靜態函數,擴展方法也可以像普通方法一樣被調用,例如 o.extensionMethod(42)

為什麼我們要添加擴展方法?它們有什麼用?您如何使用它們?為什麼我稱它們為「擴展方法」,而您也可以添加其他成員?(最後一個很簡單:我個人認為它們是擴展成員,但「擴展方法」是工作標題,而且它與其他語言中的類似功能的名稱相同,因此秉承 Dart 的良好傳統,我們選擇了熟悉且不出所料的名稱。在這裡我不會用到擴展 getter、setter 或運算子,但如果您願意,您可以完全向 String 添加一個 % 運算子,無論我們怎麼稱呼這個功能。)

由於我是設計此功能的人員之一,因此我將抓住機會在其他人有機會之前回答所有這些問題。(而且,因為我在完成此功能之前發佈了這篇文章,所以我甚至可以編輯掉那些不再正確的東西!)

但首先,我們要繞個彎路!

在擴展方法之前我會做什麼

假設,純粹假設,我認為 Future 上的 catchError 函數很糟糕,應該用更新、更閃亮、更好的東西來代替。例如,因為它將 Function 作為參數而不是適當的函數類型,這是出於完全合理的歷史原因,這意味著您不會獲得任何靜態類型檢查。這很糟糕,而且這個方法應該讓人感覺很糟糕。

顯然我不能移除這個函數,這會破壞幾乎所有曾經出現過的 Dart 程式。

那麼我至少想向 Future<T> 添加一個新方法,以便使用者可以使用它來代替,例如:

1
2
3
4
extension MyFuture<T> on Future<T> {
Future<T> onError(void Function(Object, StackTrace) onError) =>
catchError(onError);
}

您可以這樣調用它:

1
eventualInteger.onError((e, s) { ... });

遺憾的是,我不能直接將其添加到 Future 類中。如果我這樣做,我也會將其添加到 Future 介面中,而任何其他實作該介面的類將不完整,並且將無法再編譯。在某個時候,我們計算了 76 個實作 Future 的類別。那是一段時間以前的事了,我們已經停止計算了。我們仍然不能讓所有人失望,因此這個選項也不在考慮範圍內。

那麼,我會使用一個靜態輔助函數:

1
2
3
Future<T> onError<T>(Future<T> future, 
void Function(Object, StackTrace) onError) =>
future.catchError(onError);

您可以這樣調用它:

1
onError(eventualInteger, (e, s) { ... });

同樣遺憾的是,這讀起來不太好。我們喜歡使用基於 . 的方法鏈,因為它允許我們從左到右閱讀:「執行此操作,然後執行該操作,然後執行更多操作」。使用靜態輔助函數會迫使我們將其讀作:「對以下內容執行該操作:執行此操作。之後,執行更多操作」… 什麼?它沒有相同的流程,相同的schwung。在實踐中,它幾乎是不可讀的。

好吧,我沒有被嚇倒,所以我沒有改進 Future 類別,而是引入了一個新的改進的介面,並為使用者提供了一種包裝舊介面的方法:

1
2
3
4
5
6
7
8
class MyFuture<T> {
final Future<T> _wrappee;
MyFuture(this._wrappee);
// ...
MyFuture<T> onError(void Function(Object, StackTrace) onError) =>
MyFuture(_wrappee.catchError(onError));
// ... other forwarding methods
}

您可以這樣使用它:

1
MyFuture(eventualInteger).onError((e, s) { ... });

我甚至可能會讓 MyFuture 實作 Future 並將所有 Future 成員轉發到 _wrappee future,然後讓所有方法再次返回 MyFuture 包裝器,這樣我就可以繼續下去了。

如果我這麼說的話,那就太好了!

在擴展方法出現之前,這幾乎是最好的方法…這意味著手動添加包裝器,並承擔額外的包裝器物件和中間轉發函數帶來的效能損失。

我將如何使用擴展方法

一旦我們擺脫了沒有擴展的黑暗時代,我就可以使用擴展方法來獲得我真正想要的東西。我會這樣寫:

1
2
3
4
extension MyFuture<T> on Future<T> {
Future<T> onError(void Function(Object, StackTrace) onError) =>
this.catchError(onError);
}

然後您可以這樣調用它:

1
eventualInteger.onError((e, s) { ... });

就這樣。任務在五行內完成!

「但它是如何運作的?」,您可能會問。它運行得非常好,謝謝。

事實上,它的行為與包裝器類別幾乎完全相同,即使它實際上只是一個靜態輔助函數。您甚至可以顯式地寫 MyFuture(eventualInteger).onError(...),就好像擴展是一個包裝器類別一樣。它不是,但它看起來和行為幾乎就像它是。而且您可以省略顯式包裝,並在類型正確時隱式地應用它。

它(不是)一個包裝器類別

擴展聲明的設計故意使其看起來像一個類別或混入聲明,並且它的行為就像一個帶有隱藏 _wrappee 的包裝器類別一樣。您甚至可以在聲明中使用靜態成員,它們就像類別或混入聲明中的靜態成員一樣工作。

與包裝器類別相比,有一個改進:您可以在實例成員內部編寫 this 來引用 _wrappee 而不是包裝器物件。

更改 this 的含義不僅僅是一個改進。這些是靜態擴展方法,正如我之前所說的,它們只是一種更方便的調用靜態函數的方式。這意味著沒有包裝器物件。它從未存在過,我們只是假裝它存在,但這意味著我們不能讓 this 引用不存在的物件。

我們也不允許您將 MyFuture(eventualInteger) 作為一個值使用,因此如果您嘗試執行 var myFuture = MyFuture(eventualInteger),我們將不允許。使用 MyFuture(eventualInteger) 的唯一方法是作為擴展成員調用的目標。

1
2
MyFuture(eventualInteger).onError(...); // 良好:用於調用方法。
var x = MyFuture(eventualInteger); // 錯誤:用作獨立值。

這與您可以使用 super 來調用方法,但不能用於其值的方式相同。或者就像一個函式庫前綴。您所能做的就是存取一個成員;您不能將其視為一個值,因為它沒有值,也沒有值可以供它使用。

因為沒有物件,所以您不能在擴展聲明中聲明實例欄位。但是,您可以聲明 getter 和 setter,甚至可以透過 Expando 支援它們。擴展也不能聲明任何構造函數,因為沒有任何東西被構造;它只是假裝有一個構造函數接受 wrappee 物件。

它(不)擴展類型

如果您每次使用擴展成員時都必須編寫 MyFuture(...) 包裝器,那麼這將不是一個很大的改進。我們可以直接編寫包裝器類別,並花費一些編譯器工程師的時間來確保我們優化掉中間物件。

我在上面說過,您可以編寫 eventualInteger.onError(...)。這是可行的,因為我們會根據表達式的靜態類型和它們所調用成員的名稱隱式地包裝表達式。當以下所有條件都為真時,我們會自動將 expr.method() 包裝為 Ext(expr).method()

  • expr 的靜態類型沒有名稱為 method 的成員(介面始終獲勝)。
  • 擴展 Ext 已導入或在當前函式庫範圍內聲明(擴展是可存取的)。
  • 擴展聲明了一個名稱為 method 的成員,並且 expr 的靜態類型是 Ext 聲明的 on 類型的子類型(擴展是適用的)。

如果成員調用有多個可存取且適用的擴展,則有一些規則可以決定哪個擴展將在衝突中獲勝。在某些情況下,沒有辦法選擇獲勝者,那麼它只是一個編譯時錯誤。這些規則僅取決於擴展聲明的 on 類型,而不是成員聲明。(Dart 沒有「重載」——多個具有相同名稱和不同簽名的方法,您可以根據參數結構或類型在它們之間進行選擇——而擴展方法不提供後門來獲得重載。)

這一切都是靜態的

我在上面說了「靜態擴展方法」,我這樣做是有原因的!

Dart 是靜態類型的。編譯器在編譯時知道每個表達式的類型,因此如果您編寫 target.member(42),並且 member 是一個擴展成員,則編譯器需要弄清楚要隱式地將 target 包裝成哪個擴展,以便找到整個成員調用的類型。

如果隱式擴展包裝必須在找到目標表達式的類型找到成員調用的類型之間發生,那麼很明顯,「擴展推斷」必須在越來越不準確地命名為「類型推斷」的階段中發生。這個階段主要以填補缺少的泛型而聞名。

我的確寫了 eventualInteger.onError((FormatException e, s) {...}),即使 MyFuture 擴展和 onError 方法都是泛型的。在進行類型推斷時,Dart 編譯器既選擇擴展,也推斷缺少的類型參數。在這裡,它首先決定使用 MyFuture 擴展,然後插入隱式包裝器,最後對擴展應用 MyFuture(eventualInteger).onError((FormatException e, s) {...}) 執行類型推斷,其方式與對應的包裝器類別完全相同

1
2
3
4
5
6
7
8
class MyFuture<T> {
final Future<T> _wrappee;
MyFuture(this._wrappee);
Future<T> onError(void Function(Object, StackTrace) onError) =>
_wrappee.catchError(onError);
}

MyFuture(eventualInteger).onError((FormatException e, s) { ... });

在這種情況下,類型推斷將推斷以下擴展應用,並完成調用的完整類型:

1
2
MyFuture<int>(eventualInteger).onError<int>((FormatException e, s) {...});

這意味著擴展的類型參數基於被包裝表達式的靜態類型。如果您有 Future<num> fut = Future<int>.value(42);,則 fut.onError(...) 將在編譯時將 MyFutureT 類型參數綁定到 num,而不是綁定到 int。這一切都是靜態的,就像任何其他推斷的類型參數一樣。

這也意味著您將永遠無法在類型為 dynamic 的目標上調用擴展成員。

衝突解決

如上所述,當範圍內有多個適用的擴展時,有一些規則可以決定哪個擴展將獲勝。基本上,獲勝者是 on 類型最接近您正在調用成員的表達式的實際類型的擴展,有一些注意事項和決勝局。對於一起編寫的擴展,它通常「可以正常工作」。我不會詳細介紹這些細節,而是告訴您當它不能正常工作時該怎麼辦。

當兩個不同的作者為相同的類型和成員名稱編寫了衝突的擴展時,您可能會遇到問題。假設擴展 Ext1Ext2 都定義了一個適用於您的 List 物件的 bubbleSort 方法,並且衝突沒有明確的獲勝者,或者獲勝的不是您實際想要調用的那個(例如 Ext2 獲勝,而您想要調用 Ext1.bubbleSort)。那麼您必須做點什麼

最簡單的解決方案是使用顯式擴展應用:Ext1(list).bubbleSort()。這樣可以避免自動解析,只選擇您想要的那個。如果您只有少數幾個衝突,那麼這既簡單又可讀。

但是,如果您在同一個檔案中有三百個衝突,那麼您可能希望避免額外的輸入。很難更改擴展是否適用於調用,但您可以更改它是否可存取

您可以透過在導入衝突擴展(或擴展,如果您真的運氣不好)時隱藏它來做到這一點:import "ext2lib.dart" hide Ext2;。這樣做將阻止 Ext2 擴展被導入到當前的函式庫範圍內,從而使其無法存取。顯然,完全不導入 ext2lib.dart 也會這樣做,但除非擴展是您從該函式庫中使用的唯一東西,否則這是不切實際的。

(12 月 11 日編輯)在這裡我曾經說過,您可以使用前綴導入其中一個衝突的擴展,然後它將無法隱式使用。事實證明,有些人會在與他們擴展的類別相同的函式庫中聲明擴展方法,如果在使用前綴導入該函式庫時它無法工作,那就真的很煩人。所以我們修復了這個問題。使用前綴導入的擴展可以隱式地工作。如果您真的需要在同一個函式庫中使用兩個衝突的擴展,則必須在每個存在衝突的地方使用顯式擴展應用。我們可能會考慮在未來添加一種不同的方法來禁用隱式擴展,至少如果衝突的擴展成為一個反復出現的問題的話。

總結

Dart 將在即將發佈的版本中獲得擴展方法——一種調用靜態函數的漂亮方法。

您可以為實例方法運算子settergetter 定義擴展成員,但不能定義欄位

您可以顯式地調用擴展方法,或者在沒有與介面成員或其他擴展衝突時隱式地調用:

1
2
Ext1(list).bubbleSort() // 顯式,就像它是一個包裝器類別一樣。
list.bubbleSort() // 隱式,就像它擴展了類型一樣。

隱式調用與顯式調用的工作方式相同,但它們首先推斷正在應用哪個擴展。如果由於衝突的擴展而導致擴展推斷失敗,則您可以執行以下任一操作:

  • 顯式地應用擴展。
  • 完全不導入衝突的擴展(移除導入或隱藏擴展)。
  • (12 月 11 日編輯):就這樣(目前)。

擴展是靜態的。關於它們的一切都是基於靜態類型決定的。

負責任地享受!


Dart 擴展方法基礎 最初發佈在 Dart 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

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

Pub.dev 正式推出驗證發佈者

今天,我們宣布在 pub.dev(Dart 套件庫)上推出一項新功能:驗證發佈者。當您使用 具有驗證發佈者的套件時,您可以確定發佈者就是他們聲稱的身份。當您以驗證發佈者的身份發佈 套件時,您將獲得更輕鬆的套件管理的額外好處。

提升套件使用者的信任度

使用 Flutter 建構應用程式的應用程式開發人員告訴我們,擁有豐富的高品質套件選擇對他們的生產力至關重要,這使他們能夠重複使用常見的組件並存取熱門的 SDK 和函式庫。我們看到 pub.dev 生態系統的巨大增長,在過去一年中發佈了數千個套件,並且每個月都有數十萬開發人員使用 pub.dev 瀏覽和搜尋新的套件內容。

我們從套件使用者那裡聽到的最重要的選擇標準之一是誰發佈了套件。驗證發佈者透過驗證發佈者的身份,並在套件搜尋結果和套件詳細資訊頁面中清楚地列出發佈者身份(請注意下方螢幕截圖中 dart.dev 旁邊的藍色徽章),增強了這個訊號。

套件搜尋結果顯示由已驗證發佈者發佈的套件
套件詳細資訊頁面顯示由已驗證發佈者發佈的套件

當您點擊發佈者時,您可以看到更多詳細資訊,包括發佈者的聯絡電子郵件、發佈者首頁的連結,以及發佈者的簡短描述。發佈者描述由發佈者提供,提供了一個小的品牌推廣機會。

您還可以查看該發佈者發佈的所有套件的列表。以下是新的 dart.dev 發佈者 的範例。

發佈者的套件列表樣本

發佈者驗證流程

在設計驗證流程時,我們希望建立一個值得信賴、成本低廉且任何有興趣成為驗證發佈者的人都可以使用的機制。我們也希望這個流程是自動化的,這樣帳戶就可以立即建立。

在審查了幾種替代方案之後,我們決定將驗證建立在 DNS(網域名稱系統)二級網域的基礎上。我們選擇 DNS 是因為我們相信大多數套件發佈者已經擁有一個網域和一個位於該網域的首頁。在 建立發佈者的過程中,pub.dev 會根據 Google Search Console 中的現有邏輯,驗證建立驗證發佈者的使用者是否具有對相關網域的管理員存取權限。

改進的套件管理

除了驗證發佈者身份的明顯好處之外,驗證發佈者功能還提供了管理上的好處。以前,如果您發佈許多套件,存取權管理是一項繁瑣的、逐個套件的工作,這會佔用您原本可以花在改進套件上的時間。有了驗證的發佈者,您可以為您的發佈者帳戶設定一個管理員團隊,所有團隊成員都可以發佈對發佈者擁有的所有套件的更新。

我們還加入了一些新的自我管理選項,包括將現有套件移動到發佈者帳戶(如下所示)或將套件標記為 已停止

轉移現有套件

將現有套件轉移到已驗證的發佈者很容易。只需 建立一個已驗證的發佈者,並對現有套件使用 轉移功能。每個套件的這個簡單過程只需幾分鐘。

Pub.dev 路線圖

我們正在討論 pub.dev 的許多未來改進。以下是一些想法:

  • 支援特定平台(例如,Android 或 Web;#187)的套件的搜尋支援,包括更好地理解 Dart Web 與 Flutter Web 套件
  • 套件的標籤或類別 (#367)
  • 對高質量套件進行投票或點讚 (#798)
  • 舉報可疑內容的明確政策和流程 (#1570)

如果您對某個特定想法感興趣,我們鼓勵您查看 pub.dev 議題追蹤器,以確保該想法已被追蹤,並透過對議題最上面的評論加入豎起大拇指的反應來表示您的興趣。

後續步驟

如果您是套件發佈者,我們鼓勵您盡快將您的套件轉移到已驗證的發佈者帳戶,以便為您自己和您的套件使用者獲得上述好處。我們已經轉移了許多最受歡迎的 dart.dev 套件,並預計很快會轉移更多套件。

目前就這些。我們期待在 pub.dev 上看到更多高質量的套件!


宣布 pub.dev 上的驗證發佈者 最初發佈於 Medium 上的 Dart,人們在那裡透過醒目顯示和回應這個故事來繼續對話。

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

Dart 非同步程式設計:Futures

許多非同步 Dart API 會返回 futures。

Dart 用於非同步程式設計的最基本 API 之一是 futures——Future 類型的物件。在大多數情況下,Dart 的 futures 與其他語言中的 futurepromise API 非常相似。

本文討論 Dart futures 背後的概念,並告訴您如何使用 Future API。它還討論了 Flutter FutureBuilder widget,它可以根據 future 的狀態,幫助您非同步更新 Flutter UI。

由於 Dart 語言功能(例如 async-await),您可能永遠不需要直接使用 Future API。但是您幾乎肯定會在 Dart 程式碼中遇到 futures。而且您可能想要 建立 futures 或 讀取 使用 Future API 的程式碼。

本文是基於 Flutter in Focus 影片系列 Dart 中的非同步程式設計 的第二篇文章。第一篇文章,隔離區和事件迴圈,涵蓋了 Dart 支援背景工作的基礎。

如果您喜歡透過觀看或聆聽來學習,本文中的所有內容都包含在以下影片中。

您可以將 futures 想像成資料的小禮盒。有人遞給您其中一個禮盒,一開始是關閉的。過了一會兒,盒子彈開了,裡面要麼是一個值,要麼是一個錯誤。

因此,future 可以處於以下三種狀態之一:

  1. 未完成: 禮盒是 關閉的
  2. 已完成並帶有值: 禮盒是打開的,您的禮物(資料)已 準備好
  3. 已完成並帶有錯誤: 禮盒是打開的,但 出現了問題

您即將看到的大部分程式碼都圍繞著處理這三種狀態。您收到一個 future,您需要決定在盒子打開之前做什麼,在盒子打開並帶有值時做什麼,以及如果出現錯誤時做什麼。您會經常看到這種 1-2-3 模式。

future 的 3 種狀態

您可能還記得我們關於 Dart 事件迴圈 的文章中的事件迴圈(如下圖所示)。關於 futures,需要了解的一件好事是,它們實際上只是一個 API,旨在使事件迴圈的使用更容易。

Dart 事件迴圈一次處理一個事件。

您編寫的 Dart 程式碼由單個執行緒執行。在您的應用程式執行的整個過程中,那個小小的執行緒一直不斷地循環,從事件佇列中提取事件並處理它們。

假設您有一些下載按鈕的程式碼(如下所示,實現為 RaisedButton)。使用者點擊,您的按鈕開始下載圖片。

首先發生點擊事件。事件迴圈獲取事件,並調用您的點擊處理程式(您使用 onPressed 參數設定給 RaisedButton 建構函式)。您的處理程式使用 http 函式庫發出請求 (http.get()),並返回一個 future (myFuture)。

因此,現在您有了您的小盒子 myFuture。它一開始是關閉的。要註冊一個回呼函式,以便在它打開時執行,您可以使用 then()

一旦您有了您的禮盒,您就等待。也許其他事件進來了,使用者做了一些事情,而您的小盒子就放在那裡,而事件迴圈繼續循環。

最終,圖片的資料被下載,http 函式庫說:「太好了!我這裡有這個 future。」它將資料放入盒子中並彈開它,這將觸發您的回呼函式。

現在,您傳遞給 then() 的那小段程式碼執行了,它顯示了圖像。

在整個過程中,您的程式碼從未需要直接接觸事件迴圈。其他正在發生的事情,或者其他進來的事件都無關緊要。您需要做的就是從 http 函式庫獲取 future,然後說明 future 完成後要做什麼。

在實際程式碼中,您還需要處理錯誤。我們稍後會向您展示如何做到這一點。

讓我們仔細看看 Future API,其中一些您剛剛在使用中看到了。

好的,第一個問題:您如何獲得 Future 的實例?在大多數情況下,您不會直接建立 futures。這是因為許多常見的非同步程式設計任務已經有為您產生 futures 的函式庫。

例如,網路通訊返回一個 future:

1
final myFuture = http.get('http://example.com');

存取共享偏好設定也會返回一個 future:

1
final myFuture = SharedPreferences.getInstance();

但是您也可以使用 Future 建構函式來建立 futures。

Future 建構函式

最簡單的建構函式是 Future(),它接受一個函式並返回一個與函式返回類型匹配的 future。稍後,函式會非同步運行,future 會以函式的返回值完成。以下是如何使用 Future() 的示例:

讓我們添加一些列印語句以使非同步部分更清楚:

如果您在 DartPad (dartpad.dev) 中運行該程式碼,則整個 main 函式會在傳遞給 Future() 建構函式的函式之前完成。這是因為 Future() 建構函式一開始只返回一個未完成的 future。它說:「這是這個盒子。您先拿著它,稍後我會去運行您的函式,並在其中放入一些資料。」以下是上述程式碼的輸出:

1
2
Done with main().
Creating the future.

另一個建構函式 Future.value() 在您已經知道 future 的值時很方便。當您構建使用快取的服務時,此建構函式非常有用。有時您已經擁有所需的值,因此您可以將其放入其中:

1
final myFuture = Future.value(12);

Future.value() 建構函式有一個用於完成並帶有錯誤的對應項。它被稱為 Future.error(),它的工作原理基本相同,但接受一個錯誤物件和一個可選的堆疊追蹤:

1
final myFuture = Future.error(ArgumentError.notNull('input'));

最方便的 future 建構函式可能是 Future.delayed()。它的工作原理與 Future() 類似,只是它會在運行函式和完成 future 之前等待指定的時長。

使用 Future.delayed() 的一種方法是當您為測試建立模擬網路服務時。如果您需要確保您的載入微調器正確顯示,則延遲的 future 是您的朋友。

使用 futures

現在您知道了 futures 的來源,讓我們來談談如何使用它們。正如我們前面提到的,使用 future 主要與處理它的三種狀態有關:未完成,已完成並帶有值,或已完成並帶有錯誤。

以下程式碼使用 Future.delayed() 建立一個 future,該 future 在 3 秒後完成,值為 100。

當此程式碼執行時,main() 從上到下運行,建立 future 並列印「Waiting for a value…」。在整個過程中,future 都是未完成的。它在 3 秒後才會完成。

使用 完成的值,您可以使用 then()。這是每個 future 上的一個實例方法,您可以使用它來註冊一個回呼函式,以便在 future 完成並帶有值時執行。您給它一個接受單個參數的函式,該參數與 future 的類型匹配。一旦 future 完成並帶有值,您的函式將使用該值被調用。

以下是上述程式碼的輸出:

1
2
Waiting for a value... _(3 秒後回呼函式執行)_
The value is 100.

除了執行您的程式碼外,then() 還會返回一個它自己的 future,與您傳遞給它的任何函式的返回值匹配。因此,如果您需要進行幾次非同步調用,即使它們的返回類型不同,您也可以將它們鏈接在一起。

回到我們的第一個示例,如果初始 future 沒有完成並帶有值 - 如果它完成並帶有錯誤會發生什麼?then() 方法需要一個值。您需要一種方法來註冊另一個回呼函式以防出現錯誤。

答案是使用 catchError()。它的工作原理與 then() 類似,只是它接受一個錯誤而不是一個值,並且如果 future 完成並帶有錯誤,它就會執行。就像 then() 一樣,catchError() 方法也返回它自己的 future,因此您可以構建一個由 then()catchError() 方法組成的完整鏈,它們彼此等待。

注意:如果您使用 async-await 語言功能,則無需調用 then()catchError()。相反,您 await 完成的值,並使用 try-catch-finally 來處理錯誤。有關詳細資訊,請參閱 Dart 語言導覽的 非同步支援 部分。

以下是如何使用 catchError() 來處理 future 完成並帶有錯誤的情況的示例:

您甚至可以給 catchError() 一個測試函式來檢查錯誤,然後再調用回呼函式。您可以透過這種方式擁有多個 catchError() 函式,每個函式檢查不同類型的錯誤。以下是如何使用 catchError() 的可選 test 參數指定測試函式的示例:

現在您已經了解到這裡,希望您可以看到 future 的三種狀態通常如何反映在程式碼的結構中。上例中有三個程式碼塊:

  1. 第一個程式碼塊建立一個未完成的 future。
  2. 然後,如果 future 完成並帶有值,則會有一個函式被調用。
  3. 然後,如果 future 完成並帶有錯誤,則會有一個函式被調用。

還有一個您可能想要使用的方法:whenComplete()。您可以使用它在 future 完成時執行一個函式,無論它是帶有值還是錯誤。

它有點像 try-catch-finally 中的 finally 程式碼塊。如果一切順利,則會執行程式碼;如果有錯誤,則會執行程式碼;無論如何都會執行程式碼。

在 Flutter 中使用 futures

這就是您如何建立 futures,以及一些關於如何使用它們的值的資訊。現在讓我們來談談如何在 Flutter 中使用它們。

假設您有一個網路服務將返回一些 JSON 資料,並且您想要顯示該資料。您可以建立一個 StatefulWidget 來建立 future,檢查完成或錯誤,調用 setState(),並通常手動處理所有連線。

或者您可以使用 FutureBuilder。它是 Flutter SDK 附帶的一個 widget。您給它一個 future 和一個 builder 函式,它會在 future 完成時自動重建其子項。

FutureBuilder widget 的工作原理是調用其 builder 函式,該函式接受一個上下文和 future 當前狀態的快照。

您可以檢查快照以查看 future 是否完成並帶有錯誤:

否則,您可以檢查 hasData 屬性以查看它是否完成並帶有值:

如果 hasErrorhasData 都不是 true,那麼您就知道您仍在等待,您也可以為此輸出一些內容。

即使在 Flutter 程式碼中,您也可以看到這三種狀態如何不斷出現:未完成,已完成並帶有值,以及已完成並帶有錯誤。

總結

本文討論了 futures 代表什麼,以及如何使用 Future 和 FutureBuilder API 來建立 futures 並使用它們的完成值。

如果您想了解更多關於使用 futures 的資訊 - 可以使用可運行的示例和互動式練習來測試您的理解 - 請查看關於 futures、async 和 await 的非同步程式碼實驗室。

或者繼續觀看 Dart 中的非同步程式設計 系列中的下一個影片。它討論了 streams,它們與 futures 非常相似,因為它們可以提供值或錯誤。但是,futures 只給您一個結果就停止了,而 streams 則繼續運行。

非常感謝 Andrew Brogdon,他製作了本文所依據的影片。


Dart 非同步程式設計:Futures 最初發佈在 Dart 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

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

由第二季調查意見組成的文字雲 ☁️(連結到原始 圖片程式碼

我們最近進行了第六次季度使用者調查,並從超過 7,000 位 Flutter 使用者那裡收集了回應。我們發現 92.5% 的受訪者對 Flutter 感到滿意或非常滿意,略高於 上個季度!我們很興奮地看到 Flutter 的滿意度始終如一。在本文中,我們將探討有關 Flutter 生態系統的一些深入問題,因為我們認識到幫助 Flutter 社群發展生態系統非常重要。

截至 2019 年 7 月,您可以在 pub.dev 上找到超過 2,800 個依賴 Flutter 的套件。去年同期,只有約 350 個依賴 Flutter 的套件可用,這顯示了巨大的成長!而且這還不包括數千個與 Flutter 應用程式相容的其他 Dart 套件。

儘管生態系統一直在蓬勃發展,但我們認識到,在 Flutter 專案周圍建立一個出色的生態系統仍然有很多工作要做。為了更好地了解使用者的需求和困難,我們在本季度的調查中提出了一些與 Flutter 生態系統相關的問題。我們將在本文中分享這些結果,以幫助套件作者建立更多有用的套件,以滿足更多使用者的需求。

總體而言,在 5,250 位受訪者中,有 80.6% 對 Flutter 生態系統感到 非常滿意有點滿意。這還不錯,但同時它也是調查中得分最低的部分之一。

對生態系統的滿意度
對 Flutter 的整體滿意度

當被問及對 Flutter 生態系統的不滿意時,大多數受訪者選擇的原因是「我需要的關鍵套件 尚未存在」(18%),這對於相對較新的技術來說可能是可以預期的。

但是,我們很高興地發現我們的社群正在積極為 Flutter 套件生態系統做出貢獻。15% 的受訪者有為 Flutter 開發套件的經驗,而 59% 的受訪者已將他們的套件發佈到 pub.dev,這是用於分享為 Flutter 和 Dart 應用程式編寫的套件的網站。如果您已撰寫了套件,但尚未發佈,您可以閱讀 pub.dev 上的 開發套件與 Plugin,並透過發佈您的套件回饋給 Flutter 社群。這並不難 - 在那些已發佈到 pub.dev 的人中,81% 認為這 非常容易有點容易

如果您無法決定與 Flutter 社群分享哪個套件,請訪問 GitHub 上的 Flutter 儲存庫,並搜尋 標記為「會是個好套件」的議題,以查看有哪些要求。您可以為您最喜歡的請求投票,以提高其可見度。

對 Flutter 生態系統不滿意的原因(多選題)

但是,如果您有興趣幫助,還有一個更好的方法可以為生態系統做出貢獻。請注意,所有其他原因都以「我需要的關鍵套件 確實存在…」開頭,這意味著即使套件存在,套件使用者也面臨著挑戰。這告訴我們,我們可以透過改進現有套件來改進生態系統 - 透過提交錯誤、改進文件、加入缺少的功能、實作對「其他」平台的支援、加入測試等等。考慮找到一個有潛力但還不夠受重視的套件,並為它做出貢獻 - 透過測試、錯誤報告、功能貢獻或範例!

使用者對現有套件不滿意最常見的原因是「它們沒有很好的 文件」(17%)。這是社群可以幫助的另一個領域。調查問題「您希望對套件生態系統的整體體驗做些什麼改進?」產生的建議如下:

  • 加入更多樣化的程式碼使用範例
  • 加入螢幕截圖、動畫 GIF 或影片
  • 加入指向對應程式碼儲存庫的連結

以下是意見欄中的一些相關引言:

「仍然有一些套件在第一頁上沒有程式碼範例。應該強制要求至少有一個簡單的範例。」
「強調套件開發者提供更全面的套件使用方法範例。」
「強制所有套件都有一個動畫 GIF 或影片展示它(首選)或一個螢幕截圖,並且有一個範例 Dart 檔案。」
「一個套件範例的圖形顯示將十分有益。很多時候,看到套件指的是什麼比運行範例更容易。」
「希望看到「範例」部分更常被填寫。有些套件沒有任何範例。也許在這個頁面上有一個更清晰的指向對應 GitHub 儲存庫的連結?」

此外,如上圖所示,與套件實際使用相關的困難(例如相依問題、套件的錯誤、套件的設定)與選擇合適套件相關的活動(例如缺少功能、發佈者的可信度、選擇指南、足夠的平台支援)相比,對使用者的影響相對較小。

Google 的 Flutter/Dart 團隊也在調查如何改善您使用和貢獻生態系統的體驗。正在考慮的一些選項包括,但不限於:

  • 提供更好的 pub.dev 搜尋體驗
  • 讓很容易看出套件支援哪些平台
  • 提供更可靠的品質指標
  • 改善可測試性

同時,可能值得指出的是,pub.dev 上的每個套件都已經獲得了流行度、健康度和維護度的評分;這些得分有助於使用者評估套件的品質。您可以在 pub.dev/help#scoring 上找到評分系統的詳細資訊。

評分範例
維護建議

透過評分系統,套件作者可以了解如何改進套件的品質,而套件使用者可以估計套件的品質(例如,過時程度)。

我們希望評分系統隨著時間推移而擴展,以幫助使用者做出更明智的決策。更具體地說,我們希望看到加入測試覆蓋率,並且希望公開更多關於平台覆蓋率的資訊,特別是隨著 Flutter 支援的平台清單擴展。我們還希望提供一個標誌,說明特定套件是否「推薦」,以便使用者清楚了解 Flutter 社群認為值得考慮的內容。隨著這些評分變更的出現,我們將與套件作者溝通,以確保他們擁有滿足不斷提高的品質標準所需的所有資訊。

我們要向填寫了冗長調查的超過 7,000 位 Flutter 使用者表示衷心的感謝。我們學到了很多東西 - 一些其他的重點列在下面。

  • 一些 Flutter 使用者對動畫框架並不完全滿意,不是因為很難實現預期的效果,而是因為很難入門。受訪者,尤其是新使用者,不知道從哪裡開始,而且很難理解各種概念是如何相互聯繫的。因此,我們將在動畫框架的學習資料上投入更多。

  • 對於 api.flutter.dev 上的 API 文件,類別文件中的程式碼範例被評為最有用的資源。在 1.7 版本中,我們已在 API 文件中的一些類別中加入了完整的程式碼範例,但我們將繼續將此功能擴展到更多類別。(我們也接受針對 flutter/flutter 儲存庫 上的 API 文件的 PR!)

  • 最後,你們中有許多人注意到 GitHub 儲存庫中未解決的議題數量正在增加,這是 Flutter 爆發式流行的負面影響。雖然我們在上一個版本中關閉了超過 1,250 個議題,但我們在這方面還有很多工作要做。如 Flutter 1.7 部落格文章中所述,我們正在努力增加這方面的員工數量,這將有助於更快地對新錯誤進行分類、更快地關注關鍵/崩潰問題、關閉和合併重複的議題,以及將支援請求重新導向到 StackOverflow

我們重視您對調查的回應,並將在決定工作優先順序時使用這些資訊。請參加我們將於 8 月發佈的第三季度調查,該調查將探討新的主題領域。

Flutter 的 UX 研究團隊進行了各種使用者體驗研究,以便我們了解如何使您對 Flutter 的體驗更加愉快。如果您有興趣參與,請 註冊 以參加未來的研究。


Flutter 套件使用者需要什麼?來自第二季使用者調查的發現 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

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

Dart 非同步程式設計:Isolate 和事件迴圈

Dart 儘管是單執行緒語言,但它支援 futures、streams、背景工作以及你在現代非同步和(在 Flutter 的情況下)反應式編寫程式碼所需的所有其他功能。本文涵蓋 Dart 支援背景工作的基礎:isolate事件迴圈

如果您更喜歡透過觀看或聆聽來學習,本文中的所有內容都包含在以下影片中,該影片是 Flutter in Focus 影片系列 Dart 中的非同步程式設計 的一部分:

還在這裡嗎?讓我們來談談 isolate。

Isolate

Isolate 是所有 Dart 程式碼執行的環境。它就像機器上的一個小空間,擁有自己的私有記憶體區塊和一個運行事件迴圈的單執行緒。

一個 isolate 擁有自己的記憶體和一個運行事件迴圈的單執行緒。

在許多其他語言(如 C++)中,您可以讓多個執行緒共用相同的記憶體並執行您想要的任何程式碼。然而,在 Dart 中,每個執行緒都在其自己的 isolate 中,擁有自己的記憶體,並且該執行緒僅處理事件(稍後會詳細介紹)。

許多 Dart 應用程式在其單個 isolate 中運行所有程式碼,但如果需要,您可以擁有多個 isolate。如果您要執行的計算量非常大,以至於如果在主 isolate 中運行它可能會導致掉幀,那麼您可以使用 Isolate.spawn()Flutter 的 compute() 函數。這兩個函數都會建立一個單獨的 isolate 來進行數字運算,讓您的主 isolate 可以自由地(例如)重建和渲染 Widget 樹。

兩個 isolate,每個 isolate 都有自己的記憶體和執行緒。

新的 isolate 將獲得自己的事件迴圈和自己的記憶體,即使原始 isolate 是這個新 isolate 的父級,也不允許存取。這就是名稱 isolate 的來源:這些小空間彼此 隔離

事實上,isolate 合作的唯一途徑是透過來回傳遞訊息。一個 isolate 向另一個 isolate 發送訊息,接收 isolate 使用其事件迴圈處理該訊息。

Isolate 可以向其他 isolate 發送訊息。

這種缺乏共用記憶體的設計可能聽起來有點嚴格,尤其是如果您來自 Java 或 C++ 等語言,但它對 Dart 程式設計師來說有一些關鍵好處。

例如,isolate 中的記憶體分配和垃圾回收不需要鎖定。只有一個執行緒,所以如果它不忙,您就知道記憶體沒有被改變。這對於 Flutter 應用程式來說非常有用,因為它們有時需要快速地建立和銷毀大量 Widget。

事件迴圈

現在您已經對 isolate 有了基本的了解,讓我們深入了解真正使非同步程式碼成為可能的因素:事件迴圈

想像一個應用程式在其生命週期中的時間線。應用程式啟動、應用程式停止,並且在這之間會發生許多事件——來自磁碟的 I/O、來自使用者的點擊……各種各樣的事情。

您的應用程式無法預測這些事件何時發生或以何種順序發生,並且它必須使用永不阻塞的單執行緒來處理所有這些事件。因此,應用程式運行一個事件迴圈。它從其事件佇列中抓取最舊的事件,處理它,然後返回以獲取下一個事件,處理它,依此類推,直到事件佇列為空。

在應用程式運行的整個過程中——您點擊螢幕、下載內容、計時器關閉——事件迴圈只是不斷地循環,一次處理一個事件。

事件迴圈一次處理一個事件。

當動作中斷時,執行緒就會掛起,等待下一個事件。它可以觸發垃圾回收器、喝杯咖啡等等。

Dart 擁有的所有用於非同步程式設計的高階 API 和語言功能——futures、streams、async 和 await——都建立在這個簡單的迴圈之上和周圍。

例如,假設您有一個按鈕可以啟動網路請求,如下所示:

當您運行應用程式時,Flutter 會建立按鈕並將其顯示在螢幕上。然後您的應用程式等待。

您的應用程式的事件迴圈只是閒置,等待下一個事件。與按鈕無關的事件可能會進入並被處理,而按鈕則坐在那裡等待使用者點擊它。最終他們點擊了它,並且一個點擊事件進入佇列。

該事件被提取以進行處理。Flutter 查看它,渲染系統說:「這些座標與凸起的按鈕匹配」,因此 Flutter 執行 onPressed 函數。該程式碼啟動網路請求(返回一個 future)並使用 then() 方法為 future 註冊一個完成處理程式。

就這樣。迴圈完成了對該點擊事件的處理,並將其捨棄。

現在,onPressedRaisedButton 的一個屬性,網路事件使用 future 的回調函數,但這兩種技術都在做同樣的基本事情。它們都是一種告訴 Flutter 的方式:「嘿,稍後,您可能會看到特定類型的事件進入。當您看到時,請執行這段程式碼。」

因此,onPressed 正在等待點擊,future 正在等待網路資料,但從 Dart 的角度來看,這些都只是佇列中的事件。

這就是 Dart 中非同步程式碼的工作原理。Futures、streams、async 和 await——這些 API 只是您告訴 Dart 的事件迴圈:「這裡有一些程式碼,請稍後運行它」的方式。

如果我們回顧程式碼範例,您現在可以準確地看到它是如何被分解成針對特定事件的區塊的。有初始構建 (1)、點擊事件 (2) 和網路響應事件 (3)。

習慣使用非同步程式碼後,您將開始在各處識別這些模式。了解事件迴圈將有助於您繼續學習更高級別的 API。

總結

我們快速瀏覽了 isolate、事件迴圈和 Dart 中非同步程式碼的基礎。

如果您想了解更多資訊,請查看 Dart 中的非同步程式設計 系列的下一個影片。它討論了 Future API,您可以使用它來進行非同步程式設計,而無需大量程式碼。

非常感謝 Andrew Brogdon,他創作了本文所依據的影片。


Dart 非同步程式設計:Isolate 和事件迴圈 最初發佈於 Medium 上的 Dart,人們在那裡透過醒目顯示和回應這個故事來繼續對話。

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

迎接生產級 Web 應用程式的積極開發

我們對 Flutter 的願景一直是提供一個快速、高效且開放的工具集,用於在任何平台上創造出色的使用者體驗。我們從行動裝置開始,使用一個已經被成千上萬大大小小的應用程式使用的工具集。今年的 Google I/O 上,我們宣布了 Flutter 對網頁的首次預覽,允許開發人員使用相同的技能和程式碼來定位網頁瀏覽器,而且我們已經看到了 非常 多的興趣。感謝成千上萬的您,你們已經在網頁上試驗 Flutter 並給我們回饋。

我們很興奮地宣布,隨著我們越來越接近生產級網頁支援的發佈,我們正在進入開發的新階段。今天,我們 為準備在 Flutter 上認真構建網頁的企業、設計機構和新創公司開放一個專屬計畫。我們向那些準備直接參與第一波生產級 Flutter 網頁應用程式的公司提供有限的可用名額。

我們正在尋找具有引人注目的情景、計劃在未來六到十二個月內發佈基於網頁的 Flutter 體驗以及願意在行銷和發佈活動中作為展示的候選人。像任何預覽計畫一樣,不可避免地會有一些粗糙的邊緣,但我們準備提供支援和搶先體驗,以克服您遇到的任何障礙。

入選的參與者將獲得優先支援,以及在 Google/Flutter 媒體和活動中突出顯示其專案的機會。

如何申請

完成表格以申請審核。我們將從現在開始到 8 月底接受合格的申請。

更多關於 Flutter 對網頁的支援資訊,請參閱 flutter.dev/web。我們期待您的回覆!


Flutter 網頁搶先體驗計畫現已開放 最初發佈在 Flutter 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。