【文章翻譯】Future<void> vs Future<Null>, what’s the difference?

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

Future 與 Future,有什麼區別?

在 Dart 2 的諸多升級中(除了更好的靜態檢查、運行時類型安全、可選的 new/const、核心函式庫改進等等),一個值得一提的改進是 void 的正式化,使其更加實用且更不容易出錯。這在異步程式設計中尤其清晰,您可以為異步函數編寫 Future<void>,這些函數在工作完成時不返回結果。在此之前,您可能使用了 Future<Null>,所以我們經常被問到的問題是:Future<void>Future<Null> 之間有什麼區別?我應該使用哪一個,以及何時使用?

由於我的目標是提供有用的資訊,我將從 TLDR 開始。

TL;DR:99.99% 的情況下,您應該使用 void 類型。

我還建議您現在就在您的專案中啟用兩個與 void 相關的lint 規則

  • prefer_void_to_null: 幫助您改掉輸入幾乎過時的 Null 的習慣。
  • void_checks: 為您提供可能更直觀的 void 語義,即使它們對於安全程式碼並非絕對必要。

本文的其餘部分並非輕鬆閱讀。它結合了歷史、邊緣案例和類型理論。我接下來要寫的就像試圖解釋單子,但我會盡力而為,希望您能跟上。

快速 Future 小知識

為了展示閱讀本文的價值,我為您提出了一個小知識問題。

假設我們將自己限制在兩個 Future - 一個 Future<User> 和一個 Future<Null>。以下程式碼行會做什麼?

1
2
// 這段程式碼在您的編輯器中會失敗嗎?
Future<User> f1 = Future<Null>.value(null);
1
2
// 這段程式碼在運行時會失敗嗎?
Future<Null> f2 = Future<User>.value(0);

如果我們改用 await 呢?

1
2
User u = await Future<Null>.value(null);
Null n = await Future<User>.value(0);

暫停以產生戲劇性效果

答案是,除非您禁用了隱式向下轉型,否則這四行程式碼都不會在您的編輯器中顯示任何錯誤。沒錯,使用 Future<Null> 並假裝它是它不是的東西,例如 Future<User>,是 100% 合法的。Future<void> 就不是這樣!

同樣不直觀的是,第二行和第四行在運行時會失敗(以這種方式丟棄 Future 的結果是不「安全」的),而其他行則靜默地成功。

這些行為應該會讓您感到不安。但別擔心!您已經知道解決方案:使用 Future<void>。我將分析為什麼這種不直觀的行為會這樣工作。

void 現在是如何工作的

我一直在思考本文的最佳順序。這裡有很多概念需要解釋。但是,我會盡量按照從最有用的資訊到最不有用的資訊的順序進行。因此,由於 void 在 Dart 2 中比 Null 更有用,因此從 Dart 2 void 語義開始是有意義的。

Dart 中 void 背後的基本思想源於 void 值不應使用的目標。

1
2
void f() {}
print(f()); // 錯誤!

希望這就是您期望發生的!到目前為止,一切都很好。

值得注意的是,實現這個目標並且將 void 作為一個相當普通的類型公開會有點奇怪:

1
2
3
4
f(void argument) {
print(argument); // 錯誤!
// 我們可以接收參數,但我們不能使用它!
}

這就是 Dart 2 中我們新的「廣義 void」開始變得酷炫和強大的地方……儘管有時會讓人感到困惑。

我向您提出一個問題……我們可以傳遞什麼類型的值作為 f 的參數?

1
2
f(void argument) { … }
f(x); // x 是什麼?

答案是……任何東西!因為我們不允許您在函數 f 中使用 x 的值,所以我們可以自信地說 x 可以取的任何值都不會導致任何運行時錯誤。

1
2
3
4
f(1); // 與以下程式碼沒有區別
f("foo"); // 與以下程式碼沒有區別
f([1, 2, 3]); // 與以下程式碼沒有區別
// ...

如果我們根據 void 是一個不會被使用的值的思想推導出 void 的最佳語義,這就是我們得到的結果。這意味著,它是一個可以用任何東西填充的值。它就像一個真空:一個沒有輸出的輸入。

現在,在某些情況下,某些東西在運行時總是沒有錯誤,但仍然應該導致靜態錯誤。這是一個 lint 的絕佳案例!事實上,這就是 void_checks lint 背後的思想。它會尋找您將 null 以外的任何東西傳遞到 void 位置的地方,我鼓勵團隊啟用它。它並非健全性所必需的,但是將 null 以外的任何東西傳遞到 void 位置仍然可能是一個意外,lint 會為您標記它。

由於所有這些都基於基礎類型理論,因此 void 可以與 Dart 的每個部分都很好地配合使用,即使在利用類型推斷的 Future<void> 上下文中的延續也是如此:

1
2
3
4
Future<void>().then((x) {
print(x); // 錯誤!x 的類型為「void」,所以您不能列印它!
// 這就是我們想要的!
});
1
2
// 是的,這也是一個錯誤:
Foo f = await voidFuture();

此時,普通開發人員可能已經了解了有效使用 void 所需知道的一切。

然而,如果您仍在繼續閱讀,還有一些更有趣的東西。

即使啟用了 void_checks,也不能保證 void 位置包含 null。以下列覆寫為例:

1
2
3
4
5
6
7
8
class A {
void f(Object o) {}
}

class B extends A {
@override
Object f(Object o) => o;
}

我們不想讓此覆寫非法,因為它是安全的、有用的,並且與 Dart 1 相比是一個重大變更。因此,我們不得不接受 void 位置可能包含任何值。我們也不能「優化掉」A.f() 返回的值,因為在運行時它可能是一個 B!

相反,我們有一個聰明的選擇,即讓 void 成為 Object 的姊妹類型。畢竟,它可以包含任何值,並且所有值都是物件。這不是我們設計的東西,它只是現實。認識到現實,我們就可以利用它。

透過使 void 成為 Object 的同級,我們沒有任何要求 void 值完全未使用。我們盡力讓 void 保持自身,但為了向後兼容性,我們特意在一些地方放鬆了這些限制:

1
2
dynamic f() => voidFn(); // 這是合法的
voidFn() as dynamic; // 這是合法的

這些是對於 Object 合法的特殊情況,為了使 Dart 2 更順利地推出,我們也讓它對 void 合法。

使 void 成為 Object 的同級也意味著 void 可以用作類型參數,而不會使編譯後的輸出膨脹(對於 C++ 使用者來說,沒有「模板特化」)。這種減少有助於保持 Web 和 Flutter 應用程式的小巧,但代价是允許以下操作:

1
<void>[1, 2, 3].toString(); // 這是合法的,並列印 [1, 2, 3]

最後,將參數類型指定為 void 可能看起來沒有用,尤其是因為它是 Object 的一種形式。然而,void 值可以傳遞到 void 位置

1
2
f(void x) { … }
f(voidFn()); // 這是合法的

這對於排序很有用,例如在模擬返回 void 的方法時,Mockito 會使用它。(將上面的程式碼中的 f 替換為 when 以獲得更接近的近似值。)

總而言之:

  • void 類型是 Object 的同級。
  • 幾乎總是,void 物件不能被使用。
  • 標記為 void 的東西實際上可以是任何東西。
  • 任何東西都可以「丟棄」到標記為 void 的位置,而 lint void_checks 限制了這種行為。
  • void 值可以傳遞到其他 void 位置。

底部類型

在我開始討論 Null 之前,我們必須先討論「底部」類型。

這是一個類型理論中自然發生的類型,它有一個簡短的學術定義和一些實際應用。

如果您因為它太奇怪了而停止閱讀本節,那就是我最初 TLDR 的有力證據。除非您希望您的程式碼同樣奇怪,否則您可能需要 void。現在讓我們探索這個奇怪的兔子洞,看看它有多深,好嗎?

底部類型是所有類型的子類型。用更簡單的面向對象術語來說,這意味著它是一個人。還是一輛車。還是一種動物。還有來自每個程式的所有其他類型。

如果這聽起來很荒謬,那是因為它確實如此。我喜歡將它視為一個「佔位符」類型。但是「荒謬的」或「虛構的」類型也是一個合理的稱呼。然而,它被稱為底部類型,因為它是類型層次結構的底部,在電腦科學中,它是顛倒的。¯_(ツ)_/¯。正式地,它可以用符號 ⊥ 表示,您可能也認識到它是「假」的符號。

如果您試圖想像一個既是人又是又是動物的值,您將無法想到任何東西。令人驚訝的是,這就是 ⊥ 的實際用途的來源!

如果我編寫一個永不返回的函數會怎樣?有兩種簡單的方法可以做到這一點:

1
2
3
loopForever() {
while(true); // 第一種方法
}
1
2
3
alwaysThrow() {
throw Exception(); // 第二種方法
}

這兩個函數的最佳返回類型是什麼?

這取決於情況。因為函數永不返回,所以返回類型並不重要。您可以使用任何類型 - 即使是荒謬的底部類型,它可以在各種語言中以各種方式使用。

C++ 將其稱為 noreturn。Rust 有 !,Scala 有 Nothing。但我最喜歡的例子來自 Haskell,它有一個非常常用的 undefined 函數,它會中止程式。它返回,您猜對了,底部類型。

如果我們假設 Dart 中有一個 undefined 函數,就像 Haskell 中的那樣,它在被調用時只拋出一個異常並返回 ⊥,那麼它看起來像這樣:

1
Foo foo = cond ? val : undefined();

在示例用法行中,當 cond 為真時,程式可以安全地運行並儲存 val。而當 cond 為假時,程式將在 undefined() 期間拋出異常。將 undefined() 的結果「儲存」到 foo 中是安全的,無論 foo 的類型如何,因為該儲存永遠不會真正發生!

undefined() 這裡沒有返回任何東西。但這裡的教訓不是我們可以讓 foo 為空……而是底部類型像一個空的承諾一樣是空的。它比空還空。它永遠不會發生。

我必須小心地說明的一點是,您可以在實踐中從這些函數中返回 void,並且根據用法,通常應該返回 void。通常,像 return loopForever() 這樣的程式碼更有可能是一個錯誤,而不是一個有用的模式。然而,您可以自行選擇。

底部類型也適用於唯讀空列表。在 Dart 中,List 是協變的,因此允許將 List<int> 用作 List<Object>。如果您試圖將 String 放入該 List<Object> 中,運行時檢查會為您捕獲該錯誤並拋出錯誤。

這意味著,如果我們建立一個 ⊥ 列表,我們就無法在其中放入任何東西,但我們可以將其視為任何東西的列表:

1
2
3
4
List<int> intList = <⊥>[];
for (int i in intList) {
print(i * 2); // 有效,因為這永遠不會發生
}

當您查看所謂的底部類型的「逆變」位置時,還會有更多有趣的案例。

假設我們定義了一個具有 ⊥ 參數的函數:

1
void f(⊥ x) {}

這幾乎與 undefined() 示例相反。我們不是聲明了一個永不返回的函數,而是聲明了一個無法被調用的函數!沒有實際值可以賦值給該參數 x。您不能傳入 Person,因為它也不是 Car,並且您不能傳入 Car,因為它也不是 Person。您唯一可以傳入的就是荒謬的類型本身:

1
f(undefined());

但是正如我們之前所討論的,undefined() 永不返回,所以在這種情況下,f() 仍然永遠不會被實際調用!

將參數類型指定為 ⊥ 可能看起來沒有用,但它具有深奧的價值,因為所有可以被調用的函數都是不能被調用的函數的子類型。(想一想:一個可以被調用的函數不一定要被調用。如果一個函數沒有被調用,它就不會產生運行時錯誤。)

如果您還在繼續閱讀,請深呼吸,並拍拍自己的背。

具體來說,將任何 Function(X) 轉換為 Function(⊥) 是安全的,對於任何 X 都是如此。這比使用 Dart 的包羅萬象的 Function 類型更好,因為它更具體。

例如,您可以將任何一元函數儲存在一個欄位中,並動態調用它,以繞過靜態錯誤,如果您犯了錯誤,則用運行時錯誤代替它們:

1
2
Function(⊥) f;
f = (int x) => x + 1;
1
2
// 123 作為 f 的參數的有效性在運行時檢查
(f as dynamic)(123);

這是一個在緊要關頭幫助您的小技巧。

現在我們可以討論 Null 了。

Dart 2 中的 Null

概念上的「底部」類型(所有類型的子類型)存在於 Dart 中,但這並不是全部。這樣的值也存在於 Dart 2 中!在 Dart 中,我們稱它為 Null,而該值就是 - 您猜對了 - null。

由於我可以使任何東西都為 null(/Null),所以在 Dart 中荒謬的類型並不那麼荒謬。這使得它有點複雜。

注意:值 null 當然不是 Car,也不是 Person。我們確實收到了對 Dart 中非空類型的請求。因此,如果我們確實對 Dart 進行了這樣的更改,我們將需要一個新的底部類型,可能命名為 Nothing 之類的名稱,那時它將是一個更真實的底部類型。

Null 不僅具有與底部類型相同的所有荒謬用法,而且還有一個逃生出口。如果您真的需要從一個不能返回的函數中返回,或者調用一個不能被調用的函數,我們會給您一個出路!您可以將 null 傳遞進去!老實說,如果您從整體上看,我們不這樣做會有點不公平。

但是,對於一個原本簡單的聲明,它確實給出了很多警告:

1
Null nothing() => null;

Null 是 foo() 最特定的類型,所以它是一個合乎邏輯的選擇,也是一個良好的起點。如果您在其上調用不存在的方法,分析器也會善意地警告您:

1
nothing().x; // 錯誤!Null 上沒有成員 x

但这可能会造成安全的假象。

1
2
nothing().toString(); // 沒有錯誤:Null 定義了 toString()
Foo foo = nothing(); // 沒有錯誤:foo 將變為 null
1
2
3
// 對於 f(x) 中的所有參數類型都可接受,
// 如果 f(x) 需要非 null 值,則會出現運行時錯誤
f(nothing());

如果您的目標是讓 nothing() 成為

【文章翻譯】Improved discovery on the Dart package site

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

Dart 套件網站的探索功能提升

七月份時,我們談論了 Dart 套件網站 (http://pub.dartlang.org) 的變更,其中包括更好地支援分析套件的潛在問題。從那時起,我們一直專注於進一步改進在套件網站上探索內容的功能。

Dart 2不相容性

絕大多數最新的熱門套件都已遷移以支援 Dart 2,因此它們已準備好用於FlutterAngularDart 5 應用程式。然而,有些套件(大多數是未維護的舊套件)仍然不支援 Dart 2。為了確保您可以輕鬆地發現這些套件,我們現在在總覽和套件詳細資訊頁面中加入了 Dart 2 不相容標籤。

與 Dart 2 不相容的套件會清楚地標示為「Dart 2 不相容」

此外,我們預計在未來版本的套件網站中會歸檔/停止使用這些不相容的套件,因此如果您是不相容套件的作者,請考慮盡快遷移!

支援核心函式庫

Dart 附帶了一組豐富的核心(標準)函式庫。開發人員告訴我們,很難記住特定的工具函式是在核心函式庫中還是在套件中。為了幫助您找到所需的 API,pub.dartlang.org 上的搜尋現在不僅包含已發佈的套件,還包含核心函式庫。來自核心函式庫的搜尋結果在評分圈中帶有 sdk,以及註釋 Dart 核心函式庫。如果您點擊該項目,您將直接進入函式庫 API 文件。

SDK 核心函式庫中的搜尋結果會包含在結果中

套件更新 Feed

Pub 已經提供 Atom Feed 一段時間了;為了讓它更容易被發現,我們在網站頁尾加入了一個直接連結。

評分變更

我們對健康和維護的評分模型進行了一些簡化和調整。有關目前模型的概述,請參閱 pana README(pana 是提供評分的工具)。

未來,我們計畫透過直接在套件分析頁面上顯示每個報告的問題降低了整體評分的程度來提高透明度。

歡迎提供回饋

發現問題了嗎?有什麼好主意或建議嗎?請透過在我們的追蹤器中開立 issue 來告訴我們。另請注意,即使是套件網站本身也是在 pub-dartlang-dart 儲存庫 中作為開源程式碼製作的,非常歡迎貢獻和想法!


Dart 套件網站的探索功能提升 最初發佈在 Dart 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

【文章翻譯】Dart 2: Legacy of the `void`

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

Dart 2:void 的遺產

Dart 2 中類似 void 類型宇宙的半精確描述

我在 StackOverflow、Gitter 甚至 Google 內部支援頻道上看到的最常被問到的問題之一是 Dart 2 中以下內建類型之間的差異:ObjectdynamicvoidNull

長話短說,Null(或其他語言中的 Bottom,即「無」)不應該在大部分真實使用者程式碼中使用,我懷疑在不久的將來我們會看到更多文章和 lint 來溫和地阻止使用。

其餘三個類型則不太清楚,因為在 Dart 2 中,任何東西在執行時都可以是 dynamicObjectvoid,僅根據 靜態 類型簽章而有所不同。因此,讓我們看看幾個 何時 應該使用哪種類型簽章的實際範例。

Object

Object 是 Dart 類別階層的根類別,Dart 中的每個其他類別都是 Object 的子類別——包括像 intdoublebool 這樣的「原始」類型。它保證了一些東西:一個 hashCode 屬性,一個 == 運算子,一個 toString 方法

實際上,我使用 Object 作為窮人的聯合類型——期望使用者在使用某個東西之前使用 is 運算子來確定它的真實類型。**我不使用 dynamic**,因為正如下一節所述,它會停用重要的靜態分析,並且更容易讓您進入無效狀態。

1
Object readProperty(String name) { ... }
1
2
3
4
5
6
7
8
void main() {
var age = readProperty('name');
if (age is int) {
print('I am $age years old');
} else if (age is String) {
print(age);
}
}

另一個選項是使用 Object 來宣告您 不關心 資料結構的內部類型是什麼,例如 List<Object> 可能表示「任何東西的列表」。例如,在編寫一個組合 List 中每個元素的 hashCode 的函式時,這就派上用場了:

1
int hashList(List<Object> elements) { ... }

Object 的一個很好的特性(與 dynamic 相比)是,如果您嘗試在其上調用一個不存在的方法,您將立即獲得分析和編譯器回饋。例如,以下程式碼會產生一個 靜態錯誤

1
2
3
4
void main() {
Object a = 5;
a.aMethodThatDoesNotExist();
}

然而,在實際應用中,Object 是相當(並且有意地)有限的。我希望 Dart 將獲得對方法重載的支援,這將允許我在真實程式碼中顯著減少 Object 類型的使用。

dynamic

我個人在 Dart 2 中 從不 使用 dynamic 類型。在我看來,它是 Object 和一個特殊指令的聯合,該指令告訴工具和編譯器 停用靜態分析檢查。也就是說,以下程式碼是合法的,並且只會在執行時出現錯誤(而不是靜態地!):

1
2
3
4
void main() {
dynamic x = 5;
x.aMethodThatDoesNotExist();
}

在 Dart 1 中,dynamic 無處不在,任何其他靜態類型都是為了 IDE 和靜態分析支援——但編譯器(和執行時)將所有東西都視為 dynamic。儘管如此,在 Dart 2 中仍然有一些不幸的「陷阱」可能會 意外地 建立一個動態類型的變數:

1
computeAge() => 5; // 返回類型是 dynamic
1
2
3
4
void main() {
var name; // 靜態類型是 dynamic
var animals = []; // 靜態和執行時類型是 List<dynamic>
}

更糟糕的是,dynamic 調用會丟失 Dart 2 中至關重要的類型資訊:

1
2
3
class User {
String name;
}
1
2
3
4
5
6
7
void main() {
var users = []; // 隱式地是 List<dynamic>,還記得嗎?
users.add(new User()..name = 'Matan');

// 執行時錯誤:List<dynamic> 不是 Iterable<String>
Iterable<String> names = users.map((u) => u.name);
}

發生此錯誤的原因是因為這裡的實際調用是:

1
users.map((dynamic u) => u.name);

…它沒有足夠的靜態類型資訊來產生 Iterable<String>。透過將 users 修正為正確的類型(並避免動態調用),一切正常:

1
2
3
4
5
6
7
void main() {
// 我們也可以寫成 `var users = <User>[
var users = [new User()..name = 'Matan'];

// OK!
Iterable<String> names = users.map((u) => u.name);
}

void

最後,我們有 void,這是 Dart 2 中最新的類型。在 Dart 1 中,void 只能用作函式的返回類型(例如 void main()),但在 Dart 2 中,它已被 泛化,並且可以在其他地方使用,例如 Future<void>

void 類型在語義上類似於 Object(它可以是任何東西),但有一些額外的限制——void 類型不能用於任何東西(即使是 ==hashCode),並且將某個東西賦值給 void 類型是無效的:

1
2
3
4
5
void foo() {}

void main() {
var bar = foo(); // 無效
}

實際應用中,我使用 void 來表示「任何東西,我不關心元素」,或者更常見的是表示「省略」,例如在 Future<void>Stream<void> 中:

1
2
/// 清除快取。
Future<void> purgeCache() { ... }

在上面的程式碼片段中,我不希望使用者嘗試使用提供的 Future 的返回值,因為它不相關。我見過使用 Future<Null> 來達到此目的的範例,這實際上是在 Future<void> 成為可能 之前 的一種解決方法。

例如,這在靜態上是正常的,但在執行時在 Dart 2 中是無效的:

1
2
3
4
5
6
7
8
9
import 'dart:async';

Future<String> _doAThing() async => 'Test';
Future<Null> doAThing() async => _doAThing();

void main() async {
// Future<String> 不是 FutureOr<Null> 類型的子類型
await doAThing();
}

…而使用 Future<void> 作為 doAThing() 是有效且正確的。

另一個例子可能是不帶任何事件資料的 Stream

1
2
/// 當使用者登出系統時觸發事件。
Stream<void> get onLogOut { ... }

另一個更實際的用途是實作一個具有您不會使用的泛型類型引數的類別。例如,實作流行的 訪問者模式,當 C(上下文)類型引數未使用時,我們可以透過傳遞 void 來忽略它:

1
2
3
4
5
6
7
8
abstract class Visitor<N, C> {
N visitNode(N node, [C context]);
}

class IdentityVisitor<N> extends Visitor<N, void> {
@override
N visitNode(N node, [_]) => node;
}

我希望這篇簡短的文章可以幫助您圍繞使用 Objectdynamicvoid 做出 API 決策。如果您有任何其他問題或想法,請留言!


Dart 2:void 的遺產 最初發佈於 Medium 上的 Dart,人們在那裡透過突出顯示和回應這個故事來繼續討論。

【文章翻譯】Announcing Dart 2 Stable and the Dart Web Platform

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

宣佈 Dart 2 穩定版和 Dart Web 平台

今天,我們宣佈 Dart 2 穩定版正式發佈,其中包括 Dart Web 平台的重寫,它提供了生產力、效能和可擴展性的獨特組合。

Flutter 開發者已經享受 Dart 2 的許多優點,因為 Flutter 幾個月來一直在捆綁 Dart 2 SDK 的預發佈版本。然而,由於框架和語言之間的緊密耦合,Dart 2 的穩定版是 Flutter 穩定版發佈的另一個重要里程碑。如果您還沒有安裝 Flutter,請由此開始

隨著 Dart 2 的發佈,Web 開發者現在可以利用相同的語言、函式庫和工具以及許多 Web 專用的增強功能。Web 開發者應從開始使用頁面開始,以獲取有關安裝工具和建立第一個應用程式的說明。

Dart:越來越受歡迎

Dart 2 標誌著 Dart 作為一種主流程式語言的重生,專注於為行動和 Web 應用程式實現快速開發和出色的使用者體驗。我們希望讓構建客戶端應用程式的開發人員提高生產力,使用一種語言、框架和組件來減少樣板程式碼,讓他們專注於業務邏輯,並使用能夠及早識別錯誤、實現強大除錯功能並提供小型、快速執行時程式碼的工具。

在過去的一年中,Dart 經歷了巨大的成長。我們自己的分析表明,外部使用量成長了十倍。在上個季度,根據拉取請求的衡量標準,Dart 是 GitHub 上成長最快的語言之一StackOverflow 問題的成長也生動地展示了 Dart 的發展勢頭:

在內部,Dart 是 Google 用於 Web 應用程式開發的少數幾種語言之一,擁有來自數十個不同專案的數百萬行程式碼,包括 Google Ads、Google Shopping 和我們自己的內部基礎架構團隊。

事實上,您可能已經在使用 Dart 而沒有意識到:流行的 Sass 樣式表預處理器 最近用 Dart 重寫,使其更快、更可攜帶且更容易編寫。Sass 現在作為 Homebrew 和 Chocolatey 上的獨立可執行檔發佈,並在 npm 上編譯為純 JavaScript。Dart 可以輕鬆地在任何地方安裝應用程式,而無需外部相依性,並且可以融入使用 Dart 和不使用 Dart 的人的工作流程中。

Dart 2 專注於三個領域:強化和收緊語言、開發我們對 Web 和行動框架的支援,以及將支援 Google 使用 Dart 的一些工具和組件帶到外部世界。本文的其餘部分將探討這些主題。

Dart 2:客戶端優化的語言

正如我們在二月 強調的Dart 類型系統健全的。這意味著在使用者執行您的應用程式之前(在分析和編譯期間)就會發現一大類問題。當我們將 Dart 擴展到 Google 內部極其龐大的應用程式時,類型系統在開發週期的早期捕獲了更多錯誤,從而提高了生產程式碼的品質。

使用 Dart 2,您無法進入表達式求值結果與表達式的靜態類型不符的狀態。這意味著您可以將程式碼庫擴展到數百萬行,處理大型重構專案,並自信地部署程式碼。

別擔心:健全性並不意味著大量的樣板程式碼。類型系統包括進階推斷,即使是泛型類型參數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
final _field = [3.14, 6.28];

void main() {
// 沒有提供類型參數,但推斷為 <int>
print([1, 2, 3].runtimeType); // List<int>

// 如果類型不匹配,則使用它們的共同基類型
// int (1) 和 double (3.14) 都是 `num`
print([1, 3.14].runtimeType); // List<num>

// 推斷超越了變數。
// 您也可以省略欄位上的類型。
print(_field.runtimeType); // List<double>
}

要了解更多關於 Dart 2 類型系統的資訊,請查看此頁面

如先前的部落格文章所述,Dart 2 還減少了幾個關鍵領域的語言儀式:例如,new 關鍵字現在是可選的,而 const 關鍵字在常數環境中是可選的。

最後,我們在幕後做了很多工作來統一各種支援工具,使用一個共同的前端,現在支援我們的編譯器和執行時。這確保了我們使用者的現在的一致性,並承諾在 Dart 持續發展的過程中提高新功能的速度和品質。

Web 上的 Dart:豐富而強大的框架

Web 應用程式從一開始就是 Dart 使命的核心。事實上,Google 的大多數 Dart 開發都是針對 Web 應用程式。其中最大的一個是 Google Ads,它為數十億美元的網路經濟提供動力。在將他們的程式碼遷移到 Dart 的過程中,他們將 UI 程式碼庫的大小減少了 40%,同時提高了開發人員的生產力和應用程式品質。

雖然核心 Dart SDK 提供了存取現代瀏覽器 API 的函式庫,但我們也支援一個強大的受 Angular 啟發的框架,用於構建複雜的 Web 應用程式。作為 Dart 2 的一部分發佈的 AngularDart 5 利用了 Dart 的健全類型系統和新的構建系統(如下所述)在開發過程中提供快速增量構建,並在您準備部署時提供更小的已編譯 JavaScript。

此版本的一個主要重點領域是改進在頁面加載時處理的程式碼量,顯著減少了網頁的「互動時間」。與 AngularDart 4 相比,許多應用程式的程式碼大小減少了一半以上。

為了展示 Dart Web 平台的改進,我們構建了一個基於 Dart 的 HackerNews 網站客戶端實作,作為一個漸進式 Web 應用程式;這與其他範例實作一起發佈在流行的 HNPWA 網站上。使用構建 Dart Web 應用程式的最佳實務,我們能夠提供功能齊全的體驗,在現代設備上在一秒鐘內即可完全互動,在中等行動設備上在緩慢的 3G 網路上在五秒鐘內即可完全互動。即使與針對較小型應用程式進行優化的輕量級 Web 框架相比,這也具有競爭力。

hnpwa.dartlang.org

我們的框架投資延伸到我們的核心組件,我們也更新了這些組件。您現在可以存取 100 個新的類別,包括許多日期、時間和選單 Material 組件。您可以在組件庫中瀏覽我們的所有組件。

Material 日期選擇器 - Web 應用程式可用的豐富組件之一

Dart 2 的彈性工具

使用 Dart 2,Dart 成為一流的編譯為 JavaScript 的語言,具有 Web 開發人員期望的開發週期和出色的執行時效能特徵。這是同時提供大多數以 JS 為目標的語言不提供的優點:健全的類型系統和出色的原生行動應用程式支援。

使用 Dart 2,我們的 Web 工具採用 100% JavaScript 開發模型,並使用兩個互補的 JavaScript 編譯器。開發編譯器 dartdevc 提供快速增量編譯,同時產生易於閱讀和除錯的 JavaScript。這讓我們的生產編譯器 dart2js 可以專注於為行動裝置的漸進式 Web 應用程式和桌面的複雜企業體驗產生高度優化的 JavaScript。這兩種編譯器都利用 Dart 的健全類型系統來優化它們的輸出。

我們的 Web 工具基於新的構建系統構建,該系統旨在快速、可除錯且可擴展。現在,諸如產生序列化程式碼、將 Sass 編譯為 CSS 以及將 Dart 編譯為 JavaScript 之類的任務都發生在一個工具鏈中,該工具鏈支援在您變更應用程式部分內容時進行快速增量更新。構建系統也被設計為支援 Web 以外的用途。例如:Flutter 開發人員正在使用它來產生 JSON 序列化程式碼

使用 Dart 2,我們還擁有一套擴展的開發人員工具。除了支援 Android Studio 和 JetBrains 套件工具(包括 IntelliJ IDEAWebStorm)之外,我們還支援 Visual Studio CodeDart Code 擴展。我們也有一個很棒的實驗暫存區:DartPad,它已針對 Dart 2 進行了完全更新。

最後,Dart SDK 還附帶了一套其他有用的工具:與我們的套件網站一起使用的套件管理器、靜態分析器、您可以從命令列或作為套件使用的 linter,以及用於Web 文件和符合 Dart 樣式指南程式碼格式化的工具。

了解更多

查看開始使用 FlutterDart Web 工具集的說明。

發行說明提供了自 Dart 1 以來進行的許多其他較小改進的詳細技術說明,其中一些是對舊 Dart 程式碼的重大變更。如果您遇到 Google 搜尋無法解決的問題,我們建議您從 StackOverflow開始。訂閱 Dart 公告郵件列表並在 Twitter 上關注我們以獲取更新。我們也希望在我們的社群中看到您,例如 Gitter聊天室和 r/dartlang subreddit

謝謝

Dart 已成為一種針對 Web 和行動開發進行優化的通用語言。我們已經開發 Dart 2 多年,在此期間,它觸及了我們生態系統的各個方面,並且需要遷移數百萬行程式碼和數百個套件。

我們感謝 DartFlutter社群以及數百名幫助我們完成這段旅程的 Google 工程師。沒有你們,我們不可能做到這一點!


宣佈 Dart 2 穩定版和 Dart Web 平台 最初發佈於 Medium 上的 Dart,人們在那裡透過突出顯示和回應這個故事來繼續討論。

【文章翻譯】Getting ready for Dart 2, and making your packages look great on the Pub site!

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

準備迎接 Dart 2,並讓您的套件在 Pub 網站上看起來更棒!

我們發佈了 Dart 套件庫 Pub 的新版本!這次我們專注於為 Dart 2 穩定版發佈做準備。

將您的套件遷移到 Dart 2

我們預計 Dart 2 版本將在不久的將來升級到穩定版!因此,至關重要的是您要遷移您的程式碼——尤其是您已發佈的任何套件——以使其與 Dart 2 相容。這主要有三個方面:

  • 確保您的程式碼通過 Dart 2 分析(詳情)。我們建議您也要注意提示——例如,為棄用做準備。
  • 運行程式碼測試,以確保您的程式碼通過 Dart 2 運行程式碼檢查(詳情)。
  • 將套件的 SDK 約束上限更新為 <3.0.0(詳情)。

我們強烈建議所有套件作者完成這項工作,我們也將在接下來的幾天內努力完成我們擁有的套件。

Pub 網站上改進的分析介面

為了幫助您進行遷移,我們更新了 Pub 網站的分析標籤頁,使程式碼分析結果更易於閱讀。首先,在分析頁面的頂部,我們加入了進度條,讓您可以快速查看套件的狀態:

Pub 網站上顯示的套件評分

進度條下方是詳細的分析結果,按嚴重程度分組:錯誤(紅色圖示)、警告(黃色圖示)和建議問題(藍色圖示),如下圖所示:

Pub 網站上顯示的分析問題

更新的分析評分

我們還對分析評分進行了一些調整。如果符合以下任何條件,我們現在會降低套件的評分:

  • 套件未通過 Dart 2 分析。
  • SDK 約束上限不小於 <3.0.0。
  • API 參考生成(dartdoc)失敗。

對於我們的下一個 Pub 網站版本,我們預計將進一步完善評分,並加入一些關於評分計算方式的文件。

搜尋套件的 API 介面

搜尋是 Pub 網站上最受歡迎的功能——這並不奇怪。在過去的幾個季度中,我們在這一領域進行了許多改進,重點是加入平台過濾器和更好的排名。新版本擴展了搜尋功能,支援搜尋套件的 API 介面。除了目前對套件描述和 README.md 內容的搜尋索引外,搜尋現在還涵蓋了套件的所有公共 API 成員,以及這些成員的 API 文件!當您想要找到描述和 README.md 檔案中未提及的內容時,這非常棒。

例如,假設您正在尋找一個可以使用 ISO 4217 貨幣格式格式化字串的套件。在描述和 README.md 檔案中搜尋會給您一個結果,但包含 API 文件的搜尋會給您幾個結果

1
2
3
4
5
6
7
8
9
10
11
12
13
4217 的 10 個結果

intl
包含處理國際化/本地化訊息、日期和數字格式化和解析、雙向文字和其他國際化問題的程式碼。
v 0.15.2 • 更新時間:2017 年 10 月 19 日 FLUTTER WEB OTHER
API 結果:
intl/NumberFormat-class.html

flutter_billing
一個 flutter plugin,用於與 iOS 和 Android 上的計費系統進行通訊。
v 0.1.2 • 更新時間:2018 年 2 月 10 日 FLUTTER
API 結果:
flutter_billing/BillingProduct-class.html

總結…

Dart 2 穩定頻道版本即將發佈,我們已經對 Pub 網站進行了一系列與 Dart 2、排名和搜尋相關的改進。

如果您已經發佈了套件,現在 是時候更新它們以適應 Dart 2 了:

  1. 確保您的套件通過 Dart 2 分析。
  2. 在 pubspec.yaml 中將 SDK 約束上限更新為 <3.0.0。
  3. 重新發佈您更新的套件。

然後前往您在 pub.dartlang.org 上的套件分析頁面,看看您還可以做些什麼來提高您的排名!


準備迎接 Dart 2,並讓您的套件在 Pub 網站上看起來更棒! 最初發佈在 Dart 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

http://creativecommons.org/licenses/by/4.0/

【文章翻譯】Announcement: IDE Tooling for AngularDart 5

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

AngularDart 5 IDE 工具發佈公告

Dart 是一種我們熟知且喜愛的靜態類型語言,而 Dart 2 的類型將更加嚴格。這就是為什麼它成為 Angular 框架如此出色的平台的原因,在 Angular 框架中,組件大多以靜態方式連結到模板中,以建立高效能的 UI。

因此,為了進一步改善這種高效且安全的開發體驗,我們宣布推出新的 IDE 工具,以在您的模板中保留類型安全!它支援 AngularDart 5,並且與 IntelliJ/WebStorm 開箱即用。它也可以設定為在 VSCode、vim 等環境中工作。

如果您已經迫不及待,現在可以直接跳到 設定,或者繼續瀏覽我們新的 IDE 整合所提供的功能!

錯誤

新的分析插件將在您的模板中為您找到許多類型錯誤。運算式會根據您使用的指令、它們包含的輸入以及您繫結的參考(#foo、let item of、…)進行驗證。

這裡有一個拼寫錯誤的成員,因此您不必扮演人工拼寫檢查器的角色!

組件輸入上的類型不匹配也不再是問題。

我們還可以提供與 $event 變數類型相關的錯誤,

我們還會檢查您在指令中嵌入的內容。這是一個我們不僅可以捕捉類型錯誤,還可以捕捉無效程式碼的例子。

我們還可以繼續!我們可以捕捉到許多其他類型的錯誤,無論是在模板中還是在組件定義中。

除了在編輯器中突出顯示外,完整的錯誤列表還會顯示在 Dart Analysis 面板中。

自動完成

我們不僅僅停留在驗證上!雖然大部分工作是讓我們的分析能夠高效能運作,並且在編譯器方面完全正常運作,但大部分價值來自於使用已解析的模板狀態,在您的模板中提供您常用的 Dart 自動完成的優點:

但我們可以完成比普通 Dart 成員更多的內容——輸入、輸出和 HTML 標籤呢?

標籤?可以。

屬性?可以。

星號?可以。

星號內的屬性?可以。

額外加分:在組件嵌入中建議 帶有 屬性的標籤!

嵌入是 Angular 中一個眾所周知的進階主題,因此如果您認為您知道我們建議的是什麼,請為您非常了解 AngularDart 而自豪。

可以這麼說,使用我們的插件後,組件 API 更容易理解了!

導航

這裡的支援因編輯器而異,但在 IntelliJ 中,您可以點擊 Dart 運算式的各個部分。在其他編輯器中,您甚至可以導航更多實體,例如標籤和輸入/輸出。

如何使用

確保您使用的是 AngularDart 5 beta 版本,以及 Dart 2.0.0-dev.31 或更新版本。

只需將此加入到您的 analysis_options.yaml 檔案中:

1
2
3
analyzer:
plugins:
- angular

然後重新啟動您的 IDE。請注意,啟動插件可能需要幾秒鐘的時間。它首先必須為您下載原始程式碼和依賴項,並且第一次分析會比後續分析慢。

您也可以在我們的 分析器插件範例專案 中試用它。

如果您使用的是 IntelliJ,這就是您需要做的所有事情。對於其他編輯器,它們可能不會在沒有額外工作的情況下在 HTML 上運行我們的插件——例如,VSCode 需要一個 標誌

回饋和更多

我們的插件原始程式碼位於 Github 上,其中包含更多關於支援哪些功能的資訊,以及如果您有任何問題或遇到任何錯誤,可以在哪裡提交問題。

新的 IDE 工具將成為 AngularDart (v5) 的下一個穩定版本的一部分。我們非常興奮能夠在您編寫 Angular 網頁應用程式時為您提供更高的生產力!


AngularDart 5 IDE 工具發佈公告 最初發佈於 Medium 上的 Dart,人們在那裡繼續透過醒目顯示和回應這個故事來進行對話。

【文章翻譯】Announcing official gRPC support for Dart

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

宣布 Dart 官方 gRPC 支援

gRPC 是一個高效能、開源的 RPC 框架。它提供簡單的服務定義,建立在 http/2 之上,並支援雙向串流和完全整合的可插拔身份驗證。 gRPC 框架支援 多種語言,我們很高興地宣布 Dart 語言的支援現在已進入 Beta 階段!Dart gRPC 支援適用於 1.24.3 或更高版本的 Dart SDK,目前支援 FlutterVM/伺服器 平台。

建立伺服器和撰寫 gRPC 服務定義

gRPC 服務通常使用 Protocol Buffers v3 描述其端點和資料序列化。以下是一個小型範例服務定義,它定義了一個名為「Greeter」的服務,其中包含一個名為「SayHello」的 rpc 訊息(兩條訊息中的數字「1」指定了 訊息欄位 的唯一 ID):

1
2
3
4
5
6
7
8
9
10
11
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
string name = 1;
}

message HelloReply {
string message = 1;
}

定義服務後,您可以自動產生伺服器的框架:

1
protoc --dart_out=grpc:generated -Iprotos protos/greeter.proto

這會在 generated 目錄中產生一個 GreeterServiceBase 類別,然後您可以將其子類別化以加入實際的服務實作:

1
2
3
4
5
class GreeterService extends GreeterServiceBase {
@override
Future<HelloReply> sayHello(ServiceCall call, HelloRequest request) =>
Future.value(HelloReply()..message = 'Hello, ${request.name}!');
}

使用 gRPC 用戶端呼叫伺服器

當我們產生上面的服務 stub 時,protoc 編譯器也產生了一個用戶端函式庫:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import 'package:grpc/grpc.dart';
import 'package:helloworld/greeter.pbgrpc.dart';

Future<void> main() async {
final channel = ClientChannel('localhost',
port: 50051,
options: const ChannelOptions(credentials: ChannelCredentials.insecure()));
final stub = GreeterClient(channel);

final name = 'Michael';
final request = HelloRequest()..name = name;
try {
final response = await stub.sayHello(request);
print('Greeter client received: ${response.message}');
} catch (e) {
print('Caught error: $e');
}
await channel.shutdown();
}

這樣,從用戶端層呼叫服務就很簡單了:

當運行時,這將列印以下輸出:

1
Greeter client received: Hello, Michael!

後續步驟

要開始在 Dart 中使用 gRPC,請查看我們新的 Dart gRPC 快速入門,它將引導您運行和擴展 Greeter 範例。接下來,查看 Dart gRPC 教程

如果您遇到任何問題,請 提交 issue。我們也很樂意聽到您對任何您希望看到的變更或新增的回饋;例如,我們已經聽到了一些支援 gRPC-Web 協定 的請求。提供快速回饋的一種具體方法是透過按下 issue 最上方評論中的 GitHub 豎起大拇指 👍 按鈕來「投票」給 issue。

我們期待看到您使用 Dart 的 gRPC 建立的成果!

【文章翻譯】An intro to immutability with Dart

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

Dart 的不可變性介紹

不可否認,不可變性是程式設計中的一個熱門話題,尤其是在前端程式設計中。像 Immutable.js 這樣的函式庫和其他概念,像是單向資料流,都認為當資料在您不知不覺中沒有發生變化時,更容易理解資料:

在物件導向和函數式程式設計中,不可變物件(不可更改物件)是指在建立後其狀態無法修改的物件。這與可變物件(可更改物件)相反,後者可以在建立後進行修改。

那麼,Dart 呢?我們有一些概念非常適合不可變性和內建的不可變物件,從 const 修飾符和 const 建構函式開始。不要與 ES6 中的 const 混淆,後者只是一個不可變的繫結:

在 Dart 中,const 既是一個不可變的繫結,也 一個不可變的物件:

所有字面量(NullStringintdoublenumboolMapListSymbol)都可以是 const,並且可以使用 const 建構函式建立使用者類型:

讓我們回顧一下 - const 實例既是不可變的繫結,並且在語言層面上被 強制 為深度不可變的 - 並且在編譯時被 規範化 - 也就是說,任何兩個實例都被認為是等效的,並且在運行時只由一個實例表示。例如,以下程式碼相當便宜 - 它在執行時只分配一個實例:

想了解更多嗎?閱讀關於 final 和 const 的 Dart 語言導覽

使用 package:meta 進行進一步的靜態檢查

當然,const 有點限制 - 您必須能夠在編譯時建立一個類別 - 因此您不能例如讀取資料庫並在執行時建立 const 物件。我們最近在 package:meta 中引入了 @immutable 註釋:

您可以使用此註釋來幫助確保開發人員保持您的類別深度不可變。它不會像 const 那樣被規範化,但仍然對開發人員很有幫助。

希望這是一個很好的不可變性介紹。如果您想了解更多關於 Dart 或不可變性的資訊,請在評論中或 Twitter 上告訴我。


Dart 的不可變性介紹 最初發佈在 dartlang 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

【文章翻譯】Evolving the Dart REPL PoC

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

演進 Dart REPL 概念驗證

Dart REPL 允許您在 互動式 shell 中評估 Dart 表達式和語句。自從我 第一次發佈關於 Dart REPL 的文章 以來已經有一段時間了(您不需要閱讀它就能享受這篇文章),而且仍然缺少許多功能。特別是,動態導入和對頂級宣告的支援將非常有用,所以讓我們來看看如何支援它們。

免責聲明:我的確在 Google 工作,但這篇文章是關於一個個人專案。我不在 Dart 團隊或相關團隊。本文僅包含我個人的淺見。

tl;dr:關於如何運行 Dart REPL 的程式碼和說明可以在 https://github.com/BlackHC/dart_repl 找到。

熱重載

對於 Flutter,Dart VM 中加入了一項很酷的新功能:熱重載。Dart DevSummit 上有一個有趣的 YouTube 影片,詳細解釋並展示了它:

熱重載允許您在程式運行時更改程式碼。Dart VM 將擷取您所做的更改,並嘗試應用它們,同時保持一切正常運行。如果不能,它會告訴您原因。這非常酷!本著駭入 Dart 以做偉大的事情的精神,讓我們思考如何使用它來實作新功能。

為什麼我們不能在目前版本的 REPL 中導入新的函式庫?

REPL 使用 Dart 的 VM 服務來評估表達式。遺憾的是,導入函式庫在 Dart 中不是表達式,因此我們不能僅在該上下文中評估它。但是,我們可以在 REPL 的沙盒運行時更改其程式碼以導入新的函式庫,然後我們可以觸發熱重載來更新 REPL。這樣可行嗎?實際上可行 \o/

具有運行時導入的 Dart REPL

但是等等:我們透過 VM 服務評估表達式無法做的另一件事是建立新的類別和函數。事實上,由於這個原因,Dart 的任何頂級宣告都不能透過評估表達式來執行。

我們如何允許頂級宣告?

當然,我們可以使用上面描述的相同想法來加入新的類別或全域函數。但是,任何使用過 IPython 或類似工具一段時間的人都知道,您往往會在迭代程式碼時經常重新宣告相同的類別或函數。在您試用程式碼時,您會一遍又一遍地重新執行略微修改過的相同程式碼版本。

如果我們只是將這些宣告加入到我們的沙盒 Dart 函式庫中,則需要我們記錄在檔案中何時何地宣告了什麼,以便在您迭代它時更新宣告。這需要大量的邏輯和聰明的程式碼。遺憾的是,如果對類別的更改與舊程式碼或其他宣告不相容,它也容易損壞。這將阻止 REPL 熱重載,並迫使用戶重新啟動它 :( 這聽起來很複雜且脆弱:我認為這不是一個成功的組合!

Spike 和鏈

相反,如果我們可以多次重新定義相同的頂級宣告而不會發生重新宣告衝突怎麼辦?這在 Dart 中可能嗎?當然可以!但不是在同一個函式庫中 :) Dart 允許您導入一個函式庫,然後宣告一個遮蔽現有宣告的類別、函數或全局變數。

在此範例中,b.dart 的 MyClass 遮蔽 a.dart 的版本不會有任何抱怨,因為它們位於不同的函式庫中,並且 b.dart 中的本地宣告優先於從 a.dart 導入的宣告。

一般來說,當您宣告一個變數來隱藏來自外部作用域的另一個變數時,就會發生遮蔽。例如:

我們可以使用這個嗎?為了研究它,我實作了一個快速 spike 在這裡。它不會產生任何程式碼。相反,這是一個非常愚蠢的範例,用於確保我們認為可行的方法實際上可行。如果花費大量時間使用程式碼生成來實作它,卻發現它永遠不可能工作,那將會令人沮喪!這是它的要點:

這確實可行!我們可以建立一個相互導入的函式庫鏈(並且也相互匯出,因為否則符號將不會在任何地方都可用)。然後,用戶可以根據需要重新定義符號。顯然,這可能導致舊程式碼引用被遮蔽的符號,這可能會使事情稍微混亂,但至少它不會損壞。任何使用過 IPython 或類似工具的人也都學會了忍受它。它不可能那麼糟糕。

上圖顯示了它是如何工作的:當我們加入新的頂級宣告時,會建立新的「單元格」(Dart 函式庫),這些單元格導入(並匯出)前一個單元格。最終單元格被導入到沙盒函式庫中,該函式庫用作普通 Dart 表達式和語句的執行環境。沙盒檔案被就地編輯,然後使用熱重載重新載入。

工作流程願景

此外,如果您想在不遮蔽任何內容的情況下連續更新程式碼,這也是可能的:熱重載已經允許在普通 Dart 程式中使用此工作流程。您可以在 REPL 中執行相同的操作。您可以編輯您的神奇 Dart 函式庫 amazing_dart_library.dart 並將其導入 REPL,試用它,並且在您這樣做的同時,您可以在您選擇的編輯器中編輯程式碼,並讓 REPL 在您想要時透過調用 reload() 熱重載程式碼。兩全其美 \o/

我們如何在實踐中實作它?

好吧,我們在這裡駭入 Dart,所以讓我們看看:熱重載尚未得到 vm_service_client 的支援,因為它是一個如此新的功能,並且 服務規範 尚未完全完成。我開始為 Natalie(維護者)撰寫 一個 pull request 來加入對它的支援,但實際上,正如我的同事所知:生產品質的程式碼不是我的專長,尤其不是在我的業餘時間(對不起,Natalie!)。但是,這並不會阻止我們的駭客冒險。

Pub,Dart 的套件管理系統,不僅支援自動版本約束解析和套件的集中式儲存庫,還允許您 使用本地套件或直接依賴 GitHub。通常不建議這樣做,因為您會失去許多使 pub 變得偉大的東西,但在這裡它可以工作:我只是將 vm_service_client fork 到我自己的 GitHub clone 中,並進行了必要的更改。您可以在 https://github.com/BlackHC/vm_service_client/tree/reload_sources_poc 找到程式碼。之後,我更改了 Dart REPL 的 pubspec.yaml 以連結到我的 GitHub clone,而不是官方版本:

就是這樣!在終端機中簡單的 pub get 現在會更新 Dart REPL 以使用 fork 的版本。

這使得試驗任何東西都非常容易:您可以 fork 其他套件來試用,並且輕鬆地依賴它們。而且很酷的是,我可以發佈它,當您使用 pub 為自己下載 REPL 時,它也會從 GitHub 獲取程式碼。非常容易駭入,但也可以分享!(即使通常不建議用於生產套件 :) )

主要的一點是有趣的邏輯是單元格生成器,它實作了一個非常簡單的模板機制,與我們上面討論的一致:

當需要新的導入時,會從 REPL 中調用熱重載功能:

差不多就是這樣!您可以在 pull request 中查看所有更改:https://github.com/BlackHC/dart_repl/pull/2
我承認程式碼有點駭客,而且不整潔。在 pull request 中,REPL 和沙盒之間的消息傳遞也有一些無關的包裝程式碼。遺憾的是,這有點掩蓋了主要的更改。我需要看看我們如何重構所有這些,讓它再次變得更整潔…但有時,快速讓事情運行起來比撰寫最好的程式碼和 pull request 更容易。對此感到抱歉!

帶有頂級宣告的 Dart REPL

Dart REPL 的原始碼可以在 https://github.com/BlackHC/dart_repl 找到。除了支援頂級宣告之外,我還加入了對內建 importloadPackagereload 命令的支援。(請注意:loadPackage 需要即將發佈的 Dart SDK 1.24 開發構建版本。否則它就是一個無操作。)這些內建命令都是使用熱重載的簡單擴展。最後,為了從您的本地 pub 快取中載入新的套件,我使用了優秀的 pub_cache 套件。

要試用它(並假設您已經安裝了 Dart SDK),只需運行:

1
2
pub global activate dart_repl
pub global run dart_repl

感謝您閱讀到本文的結尾!請告訴我您的想法 :)

乾杯,
Andreas


演進 Dart REPL 概念驗證 最初發佈在 dartlang 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。