"

用 Go 實現 Flutter

我最近發現了 Flutter —— 谷歌的一個新的移動開發框架,我甚至曾經將 Flutter 基礎知識教給沒有編程經驗的人。Flutter 是用 Dart 編寫的,這是一種誕生於 Chrome 瀏覽器的編程語言,後來改用到了控制檯。這不禁讓我想到“Flutter 也許可以很輕易地用 Go 來實現”!

為什麼不用 Go 實現呢?Go 和 Dart 都是誕生於谷歌(並且有很多的大會分享使它們變得更好),它們都是強類型的編譯語言 —— 如果情形發生一些改變,Go 也完全可以成為像 Flutter 這樣熱門項目的選擇。而那時候 Go 會更容易地向沒有編程經驗的人解釋或傳授。

假如 Flutter 已經是用 Go 開發的。那它的代碼會是什麼樣的?

"

用 Go 實現 Flutter

我最近發現了 Flutter —— 谷歌的一個新的移動開發框架,我甚至曾經將 Flutter 基礎知識教給沒有編程經驗的人。Flutter 是用 Dart 編寫的,這是一種誕生於 Chrome 瀏覽器的編程語言,後來改用到了控制檯。這不禁讓我想到“Flutter 也許可以很輕易地用 Go 來實現”!

為什麼不用 Go 實現呢?Go 和 Dart 都是誕生於谷歌(並且有很多的大會分享使它們變得更好),它們都是強類型的編譯語言 —— 如果情形發生一些改變,Go 也完全可以成為像 Flutter 這樣熱門項目的選擇。而那時候 Go 會更容易地向沒有編程經驗的人解釋或傳授。

假如 Flutter 已經是用 Go 開發的。那它的代碼會是什麼樣的?

用 Go 實現 Flutter

Dart 的問題

自從 Dart 在 Chrome 中出現以來,我就一直在關注它的開發情況,我也一直認為 Dart 最終會在所有瀏覽器中取代 JS。2015 年,得知有關谷歌在 Chrome 中放棄 Dart 支持的消息時,我非常沮喪。

Dart 是非常奇妙的!是的,當你從 JS 升級轉向到 Dart 時,會感覺一切都還不錯;可如果你從 Go 降級轉過來,就沒那麼驚奇了,但是…… Dart 擁有非常多的特性 —— 類、泛型、異常、Futures、異步等待、事件循環、JIT、AOT、垃圾回收、重載 —— 你能想到的它都有。它有用於 getter/setter 的特殊語法、有用於構造函數自動初始化的特殊語法、有用於特殊語句的特殊語法等。

雖然它讓能讓擁有其他語言經驗的人更容易熟悉 Dart —— 這很不錯,也降低了入門門檻 —— 但我發現很難向沒有編程經驗的新手講解它。

  • 所有“特殊”的東西易被混淆 —— “名為構造方法的特殊方法”,“用於初始化的特殊語法”,“用於覆蓋的特殊語法”等等。
  • 所有“隱式”的東西令人困惑 —— “這個類是從哪兒導入的?它是隱藏的,你看不到它的實現代碼”,“為什麼我們在這個類中寫一個構造方法而不是其他方法?它在那裡,可是它是隱藏的”等等。
  • 所有“有歧義的語法”易被混淆 —— “所以我應該在這裡使用命名或者對應位置的參數嗎?”,“應該使用 final 還是用 const 進行變量聲明?”,“應該使用普通函數語法還是‘箭頭函數語法’”等等。

這三個標籤 —— “特殊”、“隱式”和“歧義” —— 可能更符合人們在編程語言中所說的“魔法”的本質。這些特性旨在幫助我們編寫更簡單、更乾淨的代碼,但實際上,它們給閱讀程序增加了更多的混亂和心智負擔。

而這正是 Go 截然不同並且有著自己強烈特色的地方。Go 實際上是一個非魔法的語言 —— 它將特殊、隱式、歧義之類的東西的數量講到最低。然而,它也有一些缺點。

Go 的問題

當我們討論 Flutter 這種 UI 框架時,我們必須把 Go 看作一個描述/指明 UI 的工具。UI 框架是一個非常複雜的主題,它需要創建一種專門的語言來處理大量的底層複雜性。最流行的方法之一是創建 DSL —— 特定領域的語言 —— 眾所周知,Go 在這方面不那麼盡如人意。

創建 DSL 意味著創建開發人員可以使用的自定義術語和謂詞。生成的代碼應該可以捕捉 UI 佈局和交互的本質,並且足夠靈活,可以應對設計師的想象流,又足夠的嚴格,符合 UI 框架的限制。例如,你應該能夠將按鈕放入容器中,然後將圖標和文本小組件放入按鈕中,可如果你試圖將按鈕放入文本中,編譯器應該給你提示一個錯誤。

特定於 UI 的語言通常也是聲明性的 —— 實際上,這意味著你應該能夠使用構造代碼(包括空格縮進!)來可視化的捕獲 UI 組件樹的結構,然後讓 UI 框架找出要運行的代碼。

有些語言更適合這樣的使用方式,而 Go 從來沒有被設計來完成這類的任務。因此,在 Go 中編寫 Flutter 代碼應該是一個相當大的挑戰!

Flutter 的優勢

如果你不熟悉 Flutter,我強烈建議你花一兩個週末的時間來觀看教程或閱讀文檔,因為它無疑會改變移動開發領域的遊戲規則。而且,可能不僅僅是移動端 —— 還有原生桌面應用程序和 web 應用程序的渲染器(用 Flutter 的術語來說就是嵌入式)。Flutter 容易學習,它是合乎邏輯的,它彙集了大量的 Material Design 強大組件庫,有活躍的社區和豐富的工具鏈(如果你喜歡“構建/測試/運行”的工作流,你也能在 Flutter 中找到同樣的“構建/測試/運行”的工作方式)還有大量其他的用於實踐的工具箱。

在一年前我需要一個相對簡單的移動應用(很明顯就是 IOS 或 Android),但我深知精通這兩個平臺開發的複雜性是非常非常大的(至少對於這個 app 是這樣),所以我不得不將其外包給另一個團隊併為此付錢。對於像我這樣一個擁有近 20 年的編程經驗的開發者來說,開發這樣的移動應用幾乎是無法忍受的。

使用 Flutter,我用了 3 個晚上的時間就編寫了同樣的應用程序,與此同時,我是從頭開始學習這個框架的!這是一個數量級的提升,也是遊戲規則的巨大改變。

我記得上一次看到類似這種開發生產力革命是在 5 年前,當時我發現了 Go。並且它改變了我的生活。

我建議你從這個很棒的視頻教程開始。

Flutter 的 Hello, world

當你用 flutter create 創建一個新的 Flutter 項目,你會得到這個“Hello, world”應用程序和代碼文本、計數器和一個按鈕,點擊增加按鈕,計數器會增加。

"

用 Go 實現 Flutter

我最近發現了 Flutter —— 谷歌的一個新的移動開發框架,我甚至曾經將 Flutter 基礎知識教給沒有編程經驗的人。Flutter 是用 Dart 編寫的,這是一種誕生於 Chrome 瀏覽器的編程語言,後來改用到了控制檯。這不禁讓我想到“Flutter 也許可以很輕易地用 Go 來實現”!

為什麼不用 Go 實現呢?Go 和 Dart 都是誕生於谷歌(並且有很多的大會分享使它們變得更好),它們都是強類型的編譯語言 —— 如果情形發生一些改變,Go 也完全可以成為像 Flutter 這樣熱門項目的選擇。而那時候 Go 會更容易地向沒有編程經驗的人解釋或傳授。

假如 Flutter 已經是用 Go 開發的。那它的代碼會是什麼樣的?

用 Go 實現 Flutter

Dart 的問題

自從 Dart 在 Chrome 中出現以來,我就一直在關注它的開發情況,我也一直認為 Dart 最終會在所有瀏覽器中取代 JS。2015 年,得知有關谷歌在 Chrome 中放棄 Dart 支持的消息時,我非常沮喪。

Dart 是非常奇妙的!是的,當你從 JS 升級轉向到 Dart 時,會感覺一切都還不錯;可如果你從 Go 降級轉過來,就沒那麼驚奇了,但是…… Dart 擁有非常多的特性 —— 類、泛型、異常、Futures、異步等待、事件循環、JIT、AOT、垃圾回收、重載 —— 你能想到的它都有。它有用於 getter/setter 的特殊語法、有用於構造函數自動初始化的特殊語法、有用於特殊語句的特殊語法等。

雖然它讓能讓擁有其他語言經驗的人更容易熟悉 Dart —— 這很不錯,也降低了入門門檻 —— 但我發現很難向沒有編程經驗的新手講解它。

  • 所有“特殊”的東西易被混淆 —— “名為構造方法的特殊方法”,“用於初始化的特殊語法”,“用於覆蓋的特殊語法”等等。
  • 所有“隱式”的東西令人困惑 —— “這個類是從哪兒導入的?它是隱藏的,你看不到它的實現代碼”,“為什麼我們在這個類中寫一個構造方法而不是其他方法?它在那裡,可是它是隱藏的”等等。
  • 所有“有歧義的語法”易被混淆 —— “所以我應該在這裡使用命名或者對應位置的參數嗎?”,“應該使用 final 還是用 const 進行變量聲明?”,“應該使用普通函數語法還是‘箭頭函數語法’”等等。

這三個標籤 —— “特殊”、“隱式”和“歧義” —— 可能更符合人們在編程語言中所說的“魔法”的本質。這些特性旨在幫助我們編寫更簡單、更乾淨的代碼,但實際上,它們給閱讀程序增加了更多的混亂和心智負擔。

而這正是 Go 截然不同並且有著自己強烈特色的地方。Go 實際上是一個非魔法的語言 —— 它將特殊、隱式、歧義之類的東西的數量講到最低。然而,它也有一些缺點。

Go 的問題

當我們討論 Flutter 這種 UI 框架時,我們必須把 Go 看作一個描述/指明 UI 的工具。UI 框架是一個非常複雜的主題,它需要創建一種專門的語言來處理大量的底層複雜性。最流行的方法之一是創建 DSL —— 特定領域的語言 —— 眾所周知,Go 在這方面不那麼盡如人意。

創建 DSL 意味著創建開發人員可以使用的自定義術語和謂詞。生成的代碼應該可以捕捉 UI 佈局和交互的本質,並且足夠靈活,可以應對設計師的想象流,又足夠的嚴格,符合 UI 框架的限制。例如,你應該能夠將按鈕放入容器中,然後將圖標和文本小組件放入按鈕中,可如果你試圖將按鈕放入文本中,編譯器應該給你提示一個錯誤。

特定於 UI 的語言通常也是聲明性的 —— 實際上,這意味著你應該能夠使用構造代碼(包括空格縮進!)來可視化的捕獲 UI 組件樹的結構,然後讓 UI 框架找出要運行的代碼。

有些語言更適合這樣的使用方式,而 Go 從來沒有被設計來完成這類的任務。因此,在 Go 中編寫 Flutter 代碼應該是一個相當大的挑戰!

Flutter 的優勢

如果你不熟悉 Flutter,我強烈建議你花一兩個週末的時間來觀看教程或閱讀文檔,因為它無疑會改變移動開發領域的遊戲規則。而且,可能不僅僅是移動端 —— 還有原生桌面應用程序和 web 應用程序的渲染器(用 Flutter 的術語來說就是嵌入式)。Flutter 容易學習,它是合乎邏輯的,它彙集了大量的 Material Design 強大組件庫,有活躍的社區和豐富的工具鏈(如果你喜歡“構建/測試/運行”的工作流,你也能在 Flutter 中找到同樣的“構建/測試/運行”的工作方式)還有大量其他的用於實踐的工具箱。

在一年前我需要一個相對簡單的移動應用(很明顯就是 IOS 或 Android),但我深知精通這兩個平臺開發的複雜性是非常非常大的(至少對於這個 app 是這樣),所以我不得不將其外包給另一個團隊併為此付錢。對於像我這樣一個擁有近 20 年的編程經驗的開發者來說,開發這樣的移動應用幾乎是無法忍受的。

使用 Flutter,我用了 3 個晚上的時間就編寫了同樣的應用程序,與此同時,我是從頭開始學習這個框架的!這是一個數量級的提升,也是遊戲規則的巨大改變。

我記得上一次看到類似這種開發生產力革命是在 5 年前,當時我發現了 Go。並且它改變了我的生活。

我建議你從這個很棒的視頻教程開始。

Flutter 的 Hello, world

當你用 flutter create 創建一個新的 Flutter 項目,你會得到這個“Hello, world”應用程序和代碼文本、計數器和一個按鈕,點擊增加按鈕,計數器會增加。

用 Go 實現 Flutter

我認為用我們假想的 Go 版的 Flutter 重寫這個例子是非常好的。它與我們的主題有密切的關聯。看一下它的代碼(它是一個文件):

lib/main.dart:

import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
複製代碼

我們先把它分解成幾個部分,分析哪些可以映射到 Go 中,哪些不能映射,並探索目前我們擁有的選項。

映射到 Go

一開始是相對比較簡單的 —— 導入依賴項並啟動 main() 函數。這裡沒有什麼挑戰性也不太有意思,只是語法上的變化:

package hello
import "github.com/flutter/flutter"
func main() {
app := NewApp()
flutter.Run(app)
}
複製代碼

唯一的不同的是不使用魔法的 MyApp() 函數,它是一個構造方法,也是一個特殊的函數,它隱藏在被稱為 MyApp 的類中,我們只是調用一個顯示定義的 NewApp() 函數 —— 它做了同樣的事情,但它更易於閱讀、理解和弄懂。

Widget 類

在 Flutter 中,一切皆 widget(小組件)。在 Flutter 的 Dart 版本中,每個小組件都代表一個類,這個類擴展了 Flutter 中特殊的 Widget 類。

Go 中沒有類,因此也沒有類層次,因為 Go 的世界不是面向對象的,更不必說類層次了。對於只熟悉基於類的 OOP 的人來說,這可能是一個不太好的情況,但也不盡然。這個世界是一個巨大的相互關聯的事物和關係圖譜。它不是混沌的,可也不是完全的結構化,並且嘗試將所有內容都放入類層次結構中可能會導致代碼難以維護,到目前為止,世界上的大多數代碼庫都是這樣子。

"

用 Go 實現 Flutter

我最近發現了 Flutter —— 谷歌的一個新的移動開發框架,我甚至曾經將 Flutter 基礎知識教給沒有編程經驗的人。Flutter 是用 Dart 編寫的,這是一種誕生於 Chrome 瀏覽器的編程語言,後來改用到了控制檯。這不禁讓我想到“Flutter 也許可以很輕易地用 Go 來實現”!

為什麼不用 Go 實現呢?Go 和 Dart 都是誕生於谷歌(並且有很多的大會分享使它們變得更好),它們都是強類型的編譯語言 —— 如果情形發生一些改變,Go 也完全可以成為像 Flutter 這樣熱門項目的選擇。而那時候 Go 會更容易地向沒有編程經驗的人解釋或傳授。

假如 Flutter 已經是用 Go 開發的。那它的代碼會是什麼樣的?

用 Go 實現 Flutter

Dart 的問題

自從 Dart 在 Chrome 中出現以來,我就一直在關注它的開發情況,我也一直認為 Dart 最終會在所有瀏覽器中取代 JS。2015 年,得知有關谷歌在 Chrome 中放棄 Dart 支持的消息時,我非常沮喪。

Dart 是非常奇妙的!是的,當你從 JS 升級轉向到 Dart 時,會感覺一切都還不錯;可如果你從 Go 降級轉過來,就沒那麼驚奇了,但是…… Dart 擁有非常多的特性 —— 類、泛型、異常、Futures、異步等待、事件循環、JIT、AOT、垃圾回收、重載 —— 你能想到的它都有。它有用於 getter/setter 的特殊語法、有用於構造函數自動初始化的特殊語法、有用於特殊語句的特殊語法等。

雖然它讓能讓擁有其他語言經驗的人更容易熟悉 Dart —— 這很不錯,也降低了入門門檻 —— 但我發現很難向沒有編程經驗的新手講解它。

  • 所有“特殊”的東西易被混淆 —— “名為構造方法的特殊方法”,“用於初始化的特殊語法”,“用於覆蓋的特殊語法”等等。
  • 所有“隱式”的東西令人困惑 —— “這個類是從哪兒導入的?它是隱藏的,你看不到它的實現代碼”,“為什麼我們在這個類中寫一個構造方法而不是其他方法?它在那裡,可是它是隱藏的”等等。
  • 所有“有歧義的語法”易被混淆 —— “所以我應該在這裡使用命名或者對應位置的參數嗎?”,“應該使用 final 還是用 const 進行變量聲明?”,“應該使用普通函數語法還是‘箭頭函數語法’”等等。

這三個標籤 —— “特殊”、“隱式”和“歧義” —— 可能更符合人們在編程語言中所說的“魔法”的本質。這些特性旨在幫助我們編寫更簡單、更乾淨的代碼,但實際上,它們給閱讀程序增加了更多的混亂和心智負擔。

而這正是 Go 截然不同並且有著自己強烈特色的地方。Go 實際上是一個非魔法的語言 —— 它將特殊、隱式、歧義之類的東西的數量講到最低。然而,它也有一些缺點。

Go 的問題

當我們討論 Flutter 這種 UI 框架時,我們必須把 Go 看作一個描述/指明 UI 的工具。UI 框架是一個非常複雜的主題,它需要創建一種專門的語言來處理大量的底層複雜性。最流行的方法之一是創建 DSL —— 特定領域的語言 —— 眾所周知,Go 在這方面不那麼盡如人意。

創建 DSL 意味著創建開發人員可以使用的自定義術語和謂詞。生成的代碼應該可以捕捉 UI 佈局和交互的本質,並且足夠靈活,可以應對設計師的想象流,又足夠的嚴格,符合 UI 框架的限制。例如,你應該能夠將按鈕放入容器中,然後將圖標和文本小組件放入按鈕中,可如果你試圖將按鈕放入文本中,編譯器應該給你提示一個錯誤。

特定於 UI 的語言通常也是聲明性的 —— 實際上,這意味著你應該能夠使用構造代碼(包括空格縮進!)來可視化的捕獲 UI 組件樹的結構,然後讓 UI 框架找出要運行的代碼。

有些語言更適合這樣的使用方式,而 Go 從來沒有被設計來完成這類的任務。因此,在 Go 中編寫 Flutter 代碼應該是一個相當大的挑戰!

Flutter 的優勢

如果你不熟悉 Flutter,我強烈建議你花一兩個週末的時間來觀看教程或閱讀文檔,因為它無疑會改變移動開發領域的遊戲規則。而且,可能不僅僅是移動端 —— 還有原生桌面應用程序和 web 應用程序的渲染器(用 Flutter 的術語來說就是嵌入式)。Flutter 容易學習,它是合乎邏輯的,它彙集了大量的 Material Design 強大組件庫,有活躍的社區和豐富的工具鏈(如果你喜歡“構建/測試/運行”的工作流,你也能在 Flutter 中找到同樣的“構建/測試/運行”的工作方式)還有大量其他的用於實踐的工具箱。

在一年前我需要一個相對簡單的移動應用(很明顯就是 IOS 或 Android),但我深知精通這兩個平臺開發的複雜性是非常非常大的(至少對於這個 app 是這樣),所以我不得不將其外包給另一個團隊併為此付錢。對於像我這樣一個擁有近 20 年的編程經驗的開發者來說,開發這樣的移動應用幾乎是無法忍受的。

使用 Flutter,我用了 3 個晚上的時間就編寫了同樣的應用程序,與此同時,我是從頭開始學習這個框架的!這是一個數量級的提升,也是遊戲規則的巨大改變。

我記得上一次看到類似這種開發生產力革命是在 5 年前,當時我發現了 Go。並且它改變了我的生活。

我建議你從這個很棒的視頻教程開始。

Flutter 的 Hello, world

當你用 flutter create 創建一個新的 Flutter 項目,你會得到這個“Hello, world”應用程序和代碼文本、計數器和一個按鈕,點擊增加按鈕,計數器會增加。

用 Go 實現 Flutter

我認為用我們假想的 Go 版的 Flutter 重寫這個例子是非常好的。它與我們的主題有密切的關聯。看一下它的代碼(它是一個文件):

lib/main.dart:

import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
複製代碼

我們先把它分解成幾個部分,分析哪些可以映射到 Go 中,哪些不能映射,並探索目前我們擁有的選項。

映射到 Go

一開始是相對比較簡單的 —— 導入依賴項並啟動 main() 函數。這裡沒有什麼挑戰性也不太有意思,只是語法上的變化:

package hello
import "github.com/flutter/flutter"
func main() {
app := NewApp()
flutter.Run(app)
}
複製代碼

唯一的不同的是不使用魔法的 MyApp() 函數,它是一個構造方法,也是一個特殊的函數,它隱藏在被稱為 MyApp 的類中,我們只是調用一個顯示定義的 NewApp() 函數 —— 它做了同樣的事情,但它更易於閱讀、理解和弄懂。

Widget 類

在 Flutter 中,一切皆 widget(小組件)。在 Flutter 的 Dart 版本中,每個小組件都代表一個類,這個類擴展了 Flutter 中特殊的 Widget 類。

Go 中沒有類,因此也沒有類層次,因為 Go 的世界不是面向對象的,更不必說類層次了。對於只熟悉基於類的 OOP 的人來說,這可能是一個不太好的情況,但也不盡然。這個世界是一個巨大的相互關聯的事物和關係圖譜。它不是混沌的,可也不是完全的結構化,並且嘗試將所有內容都放入類層次結構中可能會導致代碼難以維護,到目前為止,世界上的大多數代碼庫都是這樣子。

用 Go 實現 Flutter

我喜歡 Go 的設計者們努力重新思考這個無處不在的基於 OOP 思維,並提出了與之不同的 OOP 概念,這與 OOP 的發明者 Alan Kay 所要表達的真實意義更接近,這不是偶然。

在 Go 中,我們用一個具體的類型 —— 一個結構體來表示這種抽象:

type MyApp struct {
// ...
}
複製代碼

在一個 Flutter 的 Dart 版本中,MyApp必須繼承於 StatelessWidget 類並覆蓋它的 build 方法,這樣做有兩個作用:

  1. 自動地給予 MyApp 一些 widget 屬性/方法
  2. 通過調用 build,允許 Flutter 在其構建/渲染管道中使用跟我們的組件

我不知道 Flutter 的內部原理,所以讓我們不要懷疑我們是否能用 Go 實現它。為此,我們只有一個選擇 —— 類型嵌入

type MyApp struct {
flutter.Core
// ...
}
複製代碼

這將增加 flutter.Core 中所有導出的屬性和方法到我們的 MyApp 中。我將它稱為 Core 而不是 Widget,因為嵌入的這種類型還不能使我們的 MyApp 稱為一個 widget,而且,這是我在 Vecty GopherJS 框架中看到的類似場景的選擇。稍後我將簡要的探討 Flutter 和 Vecty 之間的相似之處。

第二部分 —— Flutter 引擎中的 build 方法 —— 當然應該簡單的通過添加方法來實現,滿足在 Go 版本的 Flutter 中定義的一些接口:

flutter.go 文件:

type Widget interface {
Build(ctx BuildContext) Widget
}
複製代碼

我們的 main.go 文件:

type MyApp struct {
flutter.Core
// ...
}
// 構建渲染 MyApp 組件。實現 Widget 的接口
func (m *MyApp) Build(ctx flutter.BuildContext) flutter.Widget {
return flutter.MaterialApp()
}
複製代碼

我們可能會注意到這裡和 Dart 版的 Flutter 有些不同:

  • 代碼更加冗長 —— BuildContext,Widget 和 MaterialApp 等方法前都明顯地提到了 flutter。
  • 代碼更簡潔 —— 沒有 extends Widget 或者 @override 子句。
  • Build 方法是大寫開頭的,因為在 Go 中它的意思是“公共”可見性。在 Dart 中,大寫開頭小寫開頭都可以,但是要使屬性或方法“私有化”,名稱需要使用下劃線(_)開頭。

為了實現一個 Go 版的 Flutter Widget,現在我們需要嵌入 flutter.Core 並實現 flutter.Widget 接口。好了,非常清楚了,我們繼續往下實現。

狀態

在 Dart 版的 Flutter 中,這是我發現的第一個令人困惑的地方。Flutter 中有兩種組件 —— StatelessWidget 和 StatefulWidget。嗯,對我來說,無狀態組件只是一個沒有狀態的組件,所以,為什麼這裡要創建一個新的類呢?好吧,我也能接受。但是你不能僅僅以相同的方式擴展 StatefulWidget,你應該執行以下神奇的操作(安裝了 Flutter 插件的 IDE 都可以做到,但這不是重點):

class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold()
}
}
複製代碼

呃,我們不僅僅要理解這裡寫的是什麼,還要理解,為什麼這樣寫?

這裡要解決的任務是向組件中添加狀態(counter)時,並允許 Flutter 在狀態更改時重繪組件。這就是複雜性的根源。

其餘的都是偶然的複雜性。Dart 版的 Flutter 中的辦法是引入一個新的 State 類,它使用泛型並以小組件作為參數。所以 _MyAppState 是一個來源於 State of a widget MyApp 的類。好了,有點道理...但是為什麼 build() 方法是在一個狀態而非組件上定義的呢?這個問題在 Flutter 倉庫的 FAQ 中有回答,這裡也有詳細的討論,概括一下就是:子類 StatefulWidget 被實例化時,為了避免 bug 之類的。換句話說,它是基於類的 OOP 設計的一種變通方法。

我們如何用 Go 來設計它呢?

首先,我個人會盡量避免為 State 創建一個新概念 —— 我們已經在任意具體類型中隱式地包含了“state” —— 它只是結構體的屬性(字段)。可以說,語言已經具備了這種狀態的概念。因此,創建一個新狀態只會讓開發人員趕到困惑 —— 為什麼我們不能在這裡使用類型的“標準狀態”。

當然,挑戰在於使 Flutter 引擎跟蹤狀態發生變化並對其作出反應(畢竟這是響應式編程的要點)。我們不需要為狀態的更改創建特殊方法和包裝器,我們只需要讓開發人員手動告訴 Flutter 何時需要更新小組件。並不是所有的狀態更改都需要立即重繪 —— 有很多典型場景能說明這個問題。我們來看看:

type MyHomePage struct {
flutter.Core
counter int
}
// Build 渲染了 MyHomePage 組件。實現了 Widget 接口
func (m *MyHomePage) Build(ctx flutter.BuildContext) flutter.Widget {
return flutter.Scaffold()
}
// 給計數器組件加一
func (m *MyHomePage) incrementCounter() {
m.counter++
flutter.Rerender(m)
// or m.Rerender()
// or m.NeedsUpdate()
}
複製代碼

這裡有很多命名和設計選項 —— 我喜歡其中的 NeedsUpdate(),因為它很明確,而且是 flutter.Core(每個組件都有它)的一個方法,但 flutter.Rerender() 也可以正常工作。它給人一種即時重繪的錯覺,但是 —— 並不會經常這樣 —— 它將在下一幀時重繪,狀態更新的頻率可能比幀的重繪的頻率高的多。

但問題是,我們只是實現了相同的任務,也就是添加一個狀態響應到小組件中,下面的一些問題還未解決:

  • 新的類型
  • 泛型
  • 讀/寫狀態的特殊規則
  • 新的特殊的方法覆蓋

另外,API 更簡潔也更明確 —— 只需增加計數器並請求 flutter 重新渲染 —— 當你要求調用特殊函數 setState 時,有些變化並不明顯,該函數返回另一個實際狀態更改的函數。同樣,隱式的魔法會有損可讀性,我們設法避免了這一點。因此,代碼更簡單,並且精簡了兩倍。

有狀態的子組件

繼續這個邏輯,讓我們仔細看看在 Flutter 中,“有狀態的小組件”是如何在另一個組件中使用的:

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
複製代碼

這裡的 MyHomePage 是一個“有狀態的小組件”(它有一個計數器),我們通過在構建過程中調用構造函數 MyHomePage(title:"...") 來創建它...等等,構建的是什麼?

調用 build() 重繪小組件,可能每秒有多次繪製。為什麼我們要在每次渲染中創建一個小組件?更別說在每次重繪循環中,重繪有狀態的小組件了。

結論是,Flutter 用小組件和狀態之間的這種分離來隱藏這個初始化/狀態記錄,不讓開發者過多關注。它確實每次都會創建一個新的 MyHomePage 組件,但它保留了原始狀態(以單例的方式),並自動找到這個“唯一”狀態,將其附加到新創建的 MyHomePage 組件上。

對我來說,這沒有多大意義 —— 更多的隱式,更多的魔法也更容易令人模糊(我們仍然可以添加小組件作為類屬性,並在創建小組件時實例化它們)。我理解為什麼這種方式不錯了(不需要跟蹤組件的子組件),並且它具有良好的簡化重構作用(只有在一個地方刪除構造函數的調用才能刪除子組件),但任何開發者試圖真正搞懂整個工作原理時,都可能會有些困惑。

對於 Go 版的 Flutter,我肯定更傾向於初始化了的狀態顯式且清晰的小組件,雖然這意味著代碼會更冗長。Dart 版的 Flutter 可能也可以實現這種方式,但我喜歡 Go 的非魔法特性,而這種哲學也適用於 Go 框架。因此,我的有狀態子組件的代碼應該類似這樣:

// MyApp 是應用頂層的組件。
type MyApp struct {
flutter.Core
homePage *MyHomePage
}
// NewMyApp 實例化一個 MyApp 組件
func NewMyApp() *MyApp {
app := &MyApp{}
app.homePage = &MyHomePage{}
return app
}
// Build 渲染了 MyApp 組件。實現了 Widget 接口
func (m *MyApp) Build(ctx flutter.BuildContext) flutter.Widget {
return m.homePage
}
// MyHomePage 是一個首頁組件
type MyHomePage struct {
flutter.Core
counter int
}
// Build 渲染 MyHomePage 組件。實現 Widget 接口
func (m *MyHomePage) Build(ctx flutter.BuildContext) flutter.Widget {
return flutter.Scaffold()
}
// 增量計數器讓 app 的計數器增加一
func (m *MyHomePage) incrementCounter() {
m.counter++
flutter.Rerender(m)
}
複製代碼

代碼更加冗長了,如果我們必須在 MyApp 中更改/替換 MyHomeWidget,那我們需要在 3 個地方有所改動,還有一個作用是,我們對代碼執行的每個階段都有一個完整而清晰的瞭解。沒有隱藏的東西在幕後發生,我們可以 100% 自信的推斷代碼、性能和每個類型以及函數的依賴關係。對於一些人來說,這就是最終目標,即編寫可靠且可維護的代碼。

順便說一下,Flutter 有一個名為 StatefulBuilder 的特殊組件,它為隱藏的狀態管理增加了更多的魔力。

DSL

現在,到了有趣的部分。我們如何在 Go 中構建一個 Flutter 的組件樹?我們希望我們的組件樹簡潔、易讀、易重構並且易於更新、描述組件之間的空間關係,增加足夠的靈活性來插入自定義代碼,比如,按下按鈕時的程序處理等等。

我認為 Dart 版的 Flutter 是非常好看的,不言自明:

return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
複製代碼

每個小組件都有一個構造方法,它接收可選的參數,而令這種聲明式方法真正好用的技巧是 函數的命名參數。

命名參數

為了防止你不熟悉,詳細說明一下,在大多數語言中,參數被稱為“位置參數”,因為它們在函數調用中的參數位置很重要:

Foo(arg1, arg2, arg3)
複製代碼

使用命名參數時,可以在函數調用中寫入它們的名稱:

Foo(name: arg1, description: arg2, size: arg3)
複製代碼

它雖增加了冗餘性,但幫你省略了你點擊跳轉函數來理解這些參數的意思。

對於 UI 組件樹,它們在可讀性方面起著至關重要的作用。考慮一下跟上面相同的代碼,在沒有命名參數的情況下:

return Scaffold(
AppBar(
Text(widget.title),
),
Center(
Column(
MainAxisAlignment.center,
<Widget>[
Text('You have pushed the button this many times:'),
Text(
'$_counter',
Theme.of(context).textTheme.display1,
),
],
),
),
FloatingActionButton(
_incrementCounter,
'Increment',
Icon(Icons.add),
),
);
複製代碼

咩,是不是?它不僅難以閱讀和理解(你需要記住每個參數的含義、類型,這是一個很大的心智負擔),而且我們在傳遞那些參數時沒有靈活性。例如,你可能不希望你的 Material 應用有 FloatingButton,所以你只是不傳遞 floatingActionButton。如果沒有命名參數,你將被迫傳遞它(例如可能是 null/nil),或者使用一些帶有反射的髒魔法來確定用戶通過構造函數傳遞了哪些參數。

由於 Go 沒有函數重載或命名參數,因此這會是一個棘手的問題。

用 Go 實現組件樹

版本 1

這個版本的例子可能只是拷貝 Dart 表示組件樹的方法,但我們真正需要的是後退一步並回答這個問題 —— 在語言的約束下,哪種方法是表示這種類型數據的最佳方法呢?

讓我們仔細看看 Scaffold 對象,它是構建外觀美觀的現代 UI 的好幫手。它有這些屬性 —— appBar,drawer,home,bottomNavigationBar,floatingActionButton —— 所有都是 Widget。我們創建類型為 Scaffold 的對象的同時初始化這些屬性。這樣看來,它與任何普通對象實例化沒有什麼不同,不是嗎?

我們用代碼實現:

return flutter.NewScaffold(
flutter.NewAppBar(
flutter.Text("Flutter Go app", nil),
),
nil,
nil,
flutter.NewCenter(
flutter.NewColumn(
flutter.MainAxisCenterAlignment,
nil,
[]flutter.Widget{
flutter.Text("You have pushed the button this many times:", nil),
flutter.Text(fmt.Sprintf("%d", m.counter), ctx.Theme.textTheme.display1),
},
),
),
flutter.FloatingActionButton(
flutter.NewIcon(icons.Add),
"Increment",
m.onPressed,
nil,
nil,
),
)
複製代碼

當然,這不是最漂亮的 UI 代碼。這裡的 flutter 是如此的豐富,以至於要求它被隱藏起來(實際上,我應該把它命名為 material 而非 flutter),這些沒有命名的參數含義並不清晰,尤其是 nil。

版本 2

由於大多數代碼都會使用 flutter 導入,所以使用導入點符號(.)的方式將 flutter 導入到我們的命名空間中是沒問題的:

import . "github.com/flutter/flutter"
複製代碼

現在,我們不用寫 flutter.Text,而只需要寫 Text。這種方式通常不是最佳實踐,但是我們使用的是一個框架,不必逐行導入,所以在這裡是一個很好的實踐。另一個有效的場景是一個基於 GoConvey 框架的 Go 測試。對我來說,框架相當於語言之上的其他語言,所以在框架中使用點符號導入也是可以的。

我們繼續往下寫我們的代碼:

return NewScaffold(
NewAppBar(
Text("Flutter Go app", nil),
),
nil,
nil,
NewCenter(
NewColumn(
MainAxisCenterAlignment,
nil,
[]Widget{
Text("You have pushed the button this many times:", nil),
Text(fmt.Sprintf("%d", m.counter), ctx.Theme.textTheme.display1),
},
),
),
FloatingActionButton(
NewIcon(icons.Add),
"Increment",
m.onPressed,
nil,
nil,
),
)
複製代碼

比較簡潔,但是那些 nil... 我們怎麼才能避免那些必須傳遞的參數?

版本 3

反射怎麼樣?一些早期的 Go Http 框架使用了這種方式(例如 martini)—— 你可以通過參數傳遞任何你想要傳遞的內容,運行時將檢查這是否是一個已知的類型/參數。從多個角度看,這不是一個好辦法 —— 它不安全,速度相對比較慢,還具魔法的特性 —— 但為了探索,我們還是試試:

return NewScaffold(
NewAppBar(
Text("Flutter Go app"),
),
NewCenter(
NewColumn(
MainAxisCenterAlignment,
[]Widget{
Text("You have pushed the button this many times:"),
Text(fmt.Sprintf("%d", m.counter), ctx.Theme.textTheme.display1),
},
),
),
FloatingActionButton(
NewIcon(icons.Add),
"Increment",
m.onPressed,
),
)
複製代碼

好吧,這跟 Dart 的原始版本有些類似,但缺少命名參數,確實會妨礙在這種情況下的可選參數的可讀性。另外,代碼本身就有些不好的跡象。

版本 4

讓我們重新思考一下,在創建新對象和可選的定義他們的屬性時,我們究竟想做什麼?這只是一個普通的變量實例,所以假如我們用另一種方式來嘗試呢:

scaffold := NewScaffold()
scaffold.AppBar = NewAppBar(Text("Flutter Go app"))
column := NewColumn()
column.MainAxisAlignment = MainAxisCenterAlignment
counterText := Text(fmt.Sprintf("%d", m.counter))
counterText.Style = ctx.Theme.textTheme.display1
column.Children = []Widget{
Text("You have pushed the button this many times:"),
counterText,
}
center := NewCenter()
center.Child = column
scaffold.Home = center
icon := NewIcon(icons.Add),
fab := NewFloatingActionButton()
fab.Icon = icon
fab.Text = "Increment"
fab.Handler = m.onPressed
scaffold.FloatingActionButton = fab
return scaffold
複製代碼

這種方法是有效的,雖然它解決了“命名參數問題”,但它也確實打亂了對組件樹的理解。首先,它顛倒了創建小組件的順序 —— 小組件越深,越應該早定義它。其次,我們丟失了基於代碼縮進的空間佈局,好的縮進佈局對於快速構建組件樹的高級預覽非常有用。

順便說一下,這種方法已經在 UI 框架中使用很長時間,比如 GTK 和 Qt。可以到最新的 Qt 5 框架的文檔中查看代碼示例。

 QGridLayout *layout = new QGridLayout(this);
layout->addWidget(new QLabel(tr("Object name:")), 0, 0);
layout->addWidget(m_objectName, 0, 1);
layout->addWidget(new QLabel(tr("Location:")), 1, 0);
m_location->setEditable(false);
m_location->addItem(tr("Top"));
m_location->addItem(tr("Left"));
m_location->addItem(tr("Right"));
m_location->addItem(tr("Bottom"));
m_location->addItem(tr("Restore"));
layout->addWidget(m_location, 1, 1);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
layout->addWidget(buttonBox, 2, 0, 1, 2);
複製代碼

所以對於一些人來說,將 UI 用代碼來描述可能是一種更自然的方式。但很難否認這肯定不是最好的選擇。

版本 5

我在想的另一個選擇,是為構造方法的參數創建一個單獨的類型。例如:

func Build() Widget {
return NewScaffold(ScaffoldParams{
AppBar: NewAppBar(AppBarParams{
Title: Text(TextParams{
Text: "My Home Page",
}),
}),
Body: NewCenter(CenterParams{
Child: NewColumn(ColumnParams{
MainAxisAlignment: MainAxisAlignment.center,
Children: []Widget{
Text(TextParams{
Text: "You have pushed the button this many times:",
}),
Text(TextParams{
Text: fmt.Sprintf("%d", m.counter),
Style: ctx.textTheme.display1,
}),
},
}),
}),
FloatingActionButton: NewFloatingActionButton(
FloatingActionButtonParams{
OnPressed: m.incrementCounter,
Tooltip: "Increment",
Child: NewIcon(IconParams{
Icon: Icons.add,
}),
},
),
})
}
複製代碼

還不錯,真的!這些 ..Params 顯得很囉嗦,但不是什麼大問題。事實上,我在 Go 的一些庫中經常遇到這種方式。當你有數個對象需要以這種方式實例化時,這種方法尤其有效。

有一種方法可以移除 ...Params 這種囉嗦的東西,但這需要語言上的改變。在 Go 中有一個建議,它的目標正是實現這一點 —— 無類型的複合型字面量。基本上,這意味著我們能夠縮短 FloattingActionButtonParameters{...} 成 {...},所以我們的代碼應該是這樣:

func Build() Widget {
return NewScaffold({
AppBar: NewAppBar({
Title: Text({
Text: "My Home Page",
}),
}),
Body: NewCenter({
Child: NewColumn({
MainAxisAlignment: MainAxisAlignment.center,
Children: []Widget{
Text({
Text: "You have pushed the button this many times:",
}),
Text({
Text: fmt.Sprintf("%d", m.counter),
Style: ctx.textTheme.display1,
}),
},
}),
}),
FloatingActionButton: NewFloatingActionButton({
OnPressed: m.incrementCounter,
Tooltip: "Increment",
Child: NewIcon({
Icon: Icons.add,
}),
},
),
})
}
複製代碼

這和 Dart 版的幾乎一樣!但是,它需要為每個小組件創建這些對應的參數類型。

版本 6

探索另一個辦法是使用小組件的方法鏈。我忘記了這個模式的名稱,但這不是很重要,因為模式應該從代碼中產生,而不是以相反的方式。

基本思想是,在創建一個小組件 —— 比如 NewButton() —— 我們立即調用一個像 WithStyle(...) 的方法,它返回相同的對象,我們就可以在一行(或一列)中調用越來越多的方法:

button := NewButton().
WithText("Click me").
WithStyle(MyButtonStyle1)
複製代碼

或者

button := NewButton().
Text("Click me").
Style(MyButtonStyle1)
複製代碼

我們嘗試用這種方法重寫基於 Scaffold 組件:

// Build renders the MyHomePage widget. Implements Widget interface.
func (m *MyHomePage) Build(ctx flutter.BuildContext) flutter.Widget {
return NewScaffold().
AppBar(NewAppBar().
Text("Flutter Go app")).
Child(NewCenter().
Child(NewColumn().
MainAxisAlignment(MainAxisCenterAlignment).
Children([]Widget{
Text("You have pushed the button this many times:"),
Text(fmt.Sprintf("%d", m.counter)).
Style(ctx.Theme.textTheme.display1),
}))).
FloatingActionButton(NewFloatingActionButton().
Icon(NewIcon(icons.Add)).
Text("Increment").
Handler(m.onPressed))
}
複製代碼

這不是一個陌生的概念 —— 例如,許多 Go 庫中對配置選項使用類似的方法。這個版本跟 Dart 的版本略有不同,但它們都具備了大部分所需要的屬性:

  • 顯示地構建組件樹
  • 命名參數
  • 在組件樹中以縮進的方式顯示組件的深度
  • 處理指定功能的能力

我也喜歡傳統的 Go 的 New...() 實例化方式。它清楚的表明它是一個函數,並創建了一個新對象。跟解釋構造函數相比,向新手解釋構造函數要更容易一些:“它是一個與類同名的函數,但是你找不到這個函數,因為它很特殊,而且你無法通過查看構造函數就輕鬆地將它與普通函數區分開來”

無論如何,在我探索的所有方法中,最後兩個選項可能是最合適的。

最終版

現在,把所有的組件組裝在一起,這就是我要說的 Flutter 的 “hello, world” 應用的樣子:

main.go

package hello
import "github.com/flutter/flutter"
func main() {
flutter.Run(NewMyApp())
}
複製代碼

app.go:

package hello
import . "github.com/flutter/flutter"
// MyApp 是頂層的應用組件
type MyApp struct {
Core
homePage *MyHomePage
}
// NewMyApp 初始化一個新的 MyApp 組件
func NewMyApp() *MyApp {
app := &MyApp{}
app.homePage = &MyHomePage{}
return app
}
// Build 渲染了 MyApp 組件。實現了 Widget 接口
func (m *MyApp) Build(ctx BuildContext) Widget {
return m.homePage
}
複製代碼

home_page.go:

package hello
import (
"fmt"
. "github.com/flutter/flutter"
)
// MyHomePage 是一個主頁組件
type MyHomePage struct {
Core
counter int
}
// Build 渲染了 MyHomePage 組件。實現了 Widget 接口
func (m *MyHomePage) Build(ctx BuildContext) Widget {
return NewScaffold(ScaffoldParams{
AppBar: NewAppBar(AppBarParams{
Title: Text(TextParams{
Text: "My Home Page",
}),
}),
Body: NewCenter(CenterParams{
Child: NewColumn(ColumnParams{
MainAxisAlignment: MainAxisAlignment.center,
Children: []Widget{
Text(TextParams{
Text: "You have pushed the button this many times:",
}),
Text(TextParams{
Text: fmt.Sprintf("%d", m.counter),
Style: ctx.textTheme.display1,
}),
},
}),
}),
FloatingActionButton: NewFloatingActionButton(
FloatingActionButtonParameters{
OnPressed: m.incrementCounter,
Tooltip: "Increment",
Child: NewIcon(IconParams{
Icon: Icons.add,
}),
},
),
})
}
// 增量計數器給 app 的計數器加一
func (m *MyHomePage) incrementCounter() {
m.counter++
flutter.Rerender(m)
}
複製代碼

實際上我很喜歡它。

結語

與 Vecty 的相似點

我不禁注意到,我的最終實現的結果跟 Vecty 框架所提供的非常相似。基本上,通用的設計幾乎是一樣的,都只是向 DOM/CSS 中輸出,而 Flutter 則成熟地深入到底層的渲染層,用漂亮的小組件提供非常流暢的 120fps 體驗(並解決了許多其他問題)。我認為 Vecty 的設計堪稱典範,難怪我實現的結果也是一個“基於Flutter 的 Vecty 變種” :)

更好的理解 Flutter 的設計

這個實驗思路本身就很有趣 —— 你不必每天都要為尚未實現的庫/框架編寫(並探索)代碼。但它也幫助我更深入的剖析了 Flutter 設計,閱讀了一些技術文檔,揭開了 Flutter 背後隱藏的魔法面紗。

Go 的不足之處

我對“ Flutter 能用 Go 來寫嗎?”的問題的答案肯定是,但我也有一些偏激,沒有意識到許多設計限制,而且這個問題沒有標準答案。我更感興趣的是探索 Dart 實現 Flutter 能給 Go 實現提供借鑑的地方。

這次實踐表明主要問題是因為 Go 語法造成的。無法調用函數時傳遞命名參數或無類型的字面量,這使得創建簡潔、結構良好的類似於 DSL 的組件樹變得更加困難和複雜。實際上,在未來的 Go 中,有 Go 提議添加命名參數,這可能是一個向後兼容的更改。有了命名參數肯定對 Go 中的 UI 框架有所幫助,但它也引入了另一個問題即學習成本,並且對每個函數定義或調用都需要考慮另一種選擇,因此這個特性所帶來的好處尚不好評估。

在 Go 中,缺少用戶定義的泛型或者缺少異常機制顯然不是什麼大問題。我會很高興聽到另一種方法,以更加簡潔和更強的可讀性來實現 Go 版的 Flutter —— 我真的很好奇有什麼方法能提供幫助。歡迎在評論區發表你的想法和代碼。

關於 Flutter 未來的一些思考

我最後的想法是,Flutter 真的是無法形容的棒,儘管我在這篇文章中指出了它的缺點。在 Flutter 中,“awesomeness/meh” 幀率是驚人的高,而且 Dart 實際上非常易於學習(如果你學過其他編程語言)。加入 Dart 的 web 家族中,我希望有一天,每一個瀏覽器附帶一個快速並且優異的 Dart VM,其內部的 Flutter 也可以作為一個 web 應用程序框架(密切關注 HummingBird 項目,本地瀏覽器支持會更好)。

大量令人難以置信的設計和優化,使 Flutter 的現狀是非常火。這是一個你夢寐以求的項目,它也有很棒並且不斷增長的社區。至少,這裡有很多好的教程,並且我希望有一天能為這個了不起的項目作出貢獻。

對我來說,它絕對是一個遊戲規則的變革者,我致力於全面的學習它,並能夠時不時地做出很棒的移動應用。即使你從未想過你自己會去開發一個移動應用,我依然鼓勵你嘗試 Flutter —— 它真的猶如一股清新的空氣。

作者:掘金翻譯計劃

鏈接:https://juejin.im/post/5d215b8df265da1b7b31ac8f

來源:掘金

著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

"

相關推薦

推薦中...