思考実験:Go on Flutter

最近では、クロスプラットフォームモバイルアプリケーションを開発するためのGoogleの新しいフレームワークであるFlutterを発見し、これまでプログラミングしたことがない人にFlutterの基本を示す機会さえありました。 Flutter自体は、コンソールの世界に逃げ込んだChromeブラウザーで生まれたDartで書かれていたので、「うーん、FlutterはGoで書けたかもしれない!」







どうして? GoとDartはどちらもGoogleが作成したもので、どちらもコンパイルされた言語を入力します。いくつかのイベントを少し変えてください。GoはFlutterのような大規模プロジェクトを実装する優れた候補です。 誰かが言うだろう-Goにはクラス、ジェネリック、例外はないので、適合しない。







だから、Flutterは既にGoで書かれているふりをしましょう。 コードはどのように見え、一般的には機能しますか?













Dartの何が問題になっていますか?



私は、ブラウザーでJavaScriptの代替として開始されて以来、この言語を使用しています。 Dartはしばらくの間Chromeブラウザに組み込まれ、JSに取って代わることが期待されていました。 2015年3月にChromeからDartサポートが削除されたことを読むのは非常に悲しいことでした。







ダーツ自体は素晴らしいです! 基本的に、JavaScriptの後はどの言語も素晴らしいですが、たとえばGoの後では、Dartはそれほど美しくありません。 でも大丈夫。 クラス、ジェネリック、例外、フューチャー、非同期待機、イベントループ、JIT / AOT、ガベージコレクター、関数オーバーロードなど、考えられるすべての機能と考えられないすべての機能を備えています確率。 Dartには、ほぼすべてのチップ用の特別な構文があります-ゲッター/セッター用の特別な構文、短縮コンストラクター用の特別な構文、特別な構文用の特別な構文など。







これにより、Dartは、一目見ただけで、以前にプログラミング言語でプログラミングした経験のある人に馴染みやすくなりました。これは素晴らしいことです。 しかし、この豊富な特殊機能を単純な「Hello、world」の例で説明しようとすると、逆に開発が複雑になることがわかりました。









原則として、この「特別な」、「隠された」、「曖昧な」三位一体は、プログラミング言語で人々が「マジック」と呼ぶものの本質を捉えています。 これらは、コードの記述を簡素化するために作成された機能ですが、実際には、読み取りと理解が複雑になります。







そして、まさにGoが他の言語とは根本的に異なる立場を取り、防御を激しく握っている領域です。 Goはほとんど魔法のない言語です。「隠された」、「特別な」、「曖昧な」言語の量は最小限に抑えられます。 しかし、Goには欠点があります。







Goの何が問題になっていますか?



Flutterについて話しているので、これはUIフレームワークであるため、UIを記述して操作するためのツールとしてGoを考えてみましょう。 一般に、UIフレームワークは大きな課題であり、ほとんどの場合、特別なソリューションが必要です。 UIで最も一般的なアプローチの1つは、 DSL (ドメイン固有言語)を作成することです。DSLは、UIのニーズに合わせて特別に調整されたライブラリまたはフレームワークの形式で実装されます。 そして、ほとんどの場合、Goは客観的にDSLにとって悪い言語であるという意見を聞くことができます。







基本的に、DSLとは、開発者が操作できる新しい言語(用語と動詞)を作成することを意味します。 上のコードは、グラフィカルインターフェースとそのコンポーネントの主要な機能を明確に説明し、デザイナーの想像力を自由に発揮できるほど柔軟であると同時に、特定のルールに従ってそれを制限できるほど厳格でなければなりません。 たとえば、コンテナにボタンを配置し、このボタンの適切な場所にアイコンを配置することができますが、ボタンをテキストなどに挿入しようとすると、コンパイラはエラーを返します。







さらに、UIを記述する言語は多くの場合宣言的です。「見たいもの」という形でインターフェースを記述し、フレームワーク自体にどのコードとどのように実行するかを理解させる機会を与えます。







一部の言語は、元々そのようなタスクを目にして開発されましたが、Goではありません。 Flutter on Goを書くこともまた別のタスクになるようです!







織田フラッター



Flutterに慣れていない場合は、次の週末に教育ビデオを見たり、チュートリアルを読んだりすることを強くお勧めします。 Flutterは、間違いなく、モバイルアプリケーションの開発におけるゲームのルールを逆転させるためです。 そして、モバイルだけでなく、FlutterアプリケーションをネイティブのdekstopアプリケーションおよびWebアプリケーションとして起動するためのレンダラー(Flutter、 embedder )が既に存在している可能性が高いです







学ぶのは簡単で、論理的で、 マテリアルデザインの美しいウィジェットの巨大なライブラリが付属しているだけでなく、素晴らしいコミュニティと優れたチューニングを備えています(Goでのgo build/run/test



go build/run/test



、Flutterでの操作が簡単な場合は)同様の体験が得られます)。







1年前、私は小さなモバイルアプリケーションを書く必要がありました(もちろんiOSとAndroid用)、両方のプラットフォーム用の高品質のアプリケーションを開発する複雑さが非常に大きいことに気付きました(アプリケーションは主な仕事ではありませんでした)-私はそれを外部委託し、お金を払わなければなりませんでした。 実際、シンプルでありながら高品質で、すべてのデバイスアプリケーションで作業することは、20年近くのプログラミング経験がある人にとっても不可能な作業でした。 そして、それは私にとって常にナンセンスでした。







Flutterを使用して、午後3時にこのアプリケーションを書き直し、フレームワーク自体をゼロから学びました。 誰かがこれが少し早いかもしれないと私に言ったなら、私はそれを信じません。







新しいテクノロジーの発見によって生産性が同様に向上したのは、5年前にGoを発見したときでした。 その瞬間は私の人生を変えました。







だから私はFlutterの学習を開始することをお勧めします。







Flutterの「Hello、World」



flutter create



を使用して新しいアプリケーションをflutter create



と、タイトル、テキスト、カウンター、およびカウンターをインクリメントするボタンを持つまさにそのようなプログラムが得られます。













これは素晴らしい例だと思います。 架空のFlutter on Goにそれを書きます。 アイデアをテストできるフレームワークのほぼすべての基本概念があります。 コードを見てみましょう(これは1つのファイルです)。







 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()



呼び出すだけです。 彼女は同じことをしますが、それが何であるか、どのように始まり、どのように機能するかを説明し理解することははるかに明確です。







ウィジェットクラス



Flutterでは、すべてがウィジェットで構成されています。 DutterバージョンのFlutterでは、各ウィジェットは、Flutterからウィジェットの特別なクラスを継承するクラスとして実装されます。







Goにはクラスがなく、それに応じてクラス階層もありません。これは、世界がオブジェクト指向ではなく、さらに階層が低いためです。 クラス指向のOOPモデルのみに精通しているプログラマーにとって、これは啓示かもしれませんが、実際にはそうではありません。 世界は、概念、プロセス、相互作用の巨大な織り交ぜたグラフです。 完全に構造化されているわけではありませんが、混oticとしているわけではありません。クラス階層にそれを詰め込もうとすることは、コードベースを読みにくく不器用にする最も信頼できる方法です。













Goの作成者がこのユビキタスなクラスの概念を再考するのに苦労し、Goで実装されたOOPのはるかにシンプルで強力な概念を実装したため、Goは本当に感謝しています







Goでは、特定のタイプ-構造の形式で抽象化を表します。







 type MyApp struct { // ... }
      
      





MyApp



バージョンのFlutterでは、 MyApp



StatelessWidget



を継承し、 build



メソッドをオーバーライドする必要があります。 これは、2つの問題を解決するために必要です。







  1. ウィジェット( MyApp



    )にいくつかの特別なプロパティ/メソッドを与えます
  2. Flutterがビルド/レンダリングプロセスでコードを呼び出すことができるようにします


Flutterの内部はわからないので、アイテム番号1は問題ではないので、やらなければなりません。 Goには、このためのユニークで明白な解決策があります: 埋め込み型:







 type MyApp struct { flutter.Core // ... }
      
      





このコードは、すべてのflutter.Core



プロパティとメソッドをMyApp



タイプに追加しMyApp



。 私はそれをWidget



代わりにCore



と呼びました。第一に、タイプ埋め込みはMyApp



まだウィジェットにしないからです。第二に、この名前はGopherJS Vectyフレームワーク(Go専用のReactのようなもの)で非常によく使用されます。 VectyとFlutterの類似性については、後ほど触れます。







2番目のポイント-Flutterエンジンを使用できるbuild()



メソッドの実装も、Goで簡単かつ明確に解決されます。 Goの架空のFlutterライブラリのどこかで定義された特定のインターフェイスを満たす特定のシグネチャを持つメソッドを追加するだけです。







flutter.go:







 type Widget interface { Build(ctx BuildContext) Widget }
      
      





そして今、main.go:







 type MyApp struct { flutter.Core // ... } // Build renders the MyApp widget. Implements Widget interface. func (m *MyApp) Build(ctx flutter.BuildContext) flutter.Widget { return flutter.MaterialApp() }
      
      





ここでいくつかの違いに気付くことができます:









したがって、Flutter on Goでウィジェットを作成するには、 flutter.Core



型を埋め込み、 flutter.Widget



インターフェイスを実装するflutter.Widget



ます。 私たちはそれを理解し、さらに掘り下げました。







状態



これはFlutterで私を本当に混乱させたものの1つでした。 StatelessWidget



StatefulWidget



2つの異なるクラスがあります。 私に関しては、「ステートレスウィジェット」は同じウィジェットですが、うーん、データ、状態がありません。なぜ新しいクラスを考え出す必要があるのでしょうか。 しかし、大丈夫、私はそれで生きることができます。







しかし、さらに-さらに、別のクラス( StatefulWidget



)を継承することはできませんが、そのような魔法を記述する必要があります(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() } }
      
      





うーん、ここで何が起こるか見てみましょう。







基本的に、タスクは次のとおりです。ウィジェット(この場合はカウンター)に状態を追加し、ウィジェットを再描画するために状態を変更したときにFlutterエンジンに通知します。 これが問題の実際の複雑さです(Brooksの用語での本質的な複雑さ)。







他のすべては偶然の複雑さです。 DartのFlutterには、ジェネリックを使用してウィジェットを型パラメーターとして受け取る新しいState



クラスがあります。 次に、 _MyHomePageState



クラスが_MyHomePageState



。このクラスはState MyApp



を継承State MyApp



... _MyHomePageState



、それでも何らかの方法でダイジェストできます。 しかし、なぜウィジェットが存在するクラスではなく、Stateクラスでbuild()



メソッドが定義されているのでしょうか? Brrr ....







Flutter FAQにはこの質問に対する答えがあり、 ここでは短い答えを十分に詳細に検討ます StatefulWidget



継承するときに特定のクラスのバグを回避するためです。 つまり、これはクラス指向のOOP設計の問題を解決するための回避策です。 シック。







Goでこれをどのように行いますか?







第一に、私は個人的には「州」に別のエンティティを作成しないことを好みます- State



。 結局、特定のタイプごとにすでに状態があります-これらは構造体の単なるフィールドです。 言語は、いわばこのエッセンスをすでに与えてくれました。 同様の別のエンティティを作成すると、プログラマが混乱するだけです。







もちろん、課題は、Flutterに状態の変化に対応する機能を提供することです(これは結局、リアクティブプログラミングの本質です)。 そして、開発者に特別な関数( setState()



)の使用を「依頼」できる場合、同じ方法で、いつ再描画するかしないかをエンジンに伝えるために、特別な関数を使用するように依頼できます。 最終的に、すべての状態の変更が再描画を必要とするわけではありません。ここでさらに制御できます。







 type MyHomePage struct { flutter.Core counter int } // Build renders the MyHomePage widget. Implements Widget interface. func (m *MyHomePage) Build(ctx flutter.BuildContext) flutter.Widget { return flutter.Scaffold() } // incrementCounter increments widgets's counter by one. func (m *MyHomePage) incrementCounter() { m.counter++ flutter.Rerender(m) // or m.Rerender() // or m.NeedsUpdate() }
      
      





さまざまな命名オプションをNeedsUpdate()



ことができます-直接性と、これがウィジェットプロパティ( flutter.Core



からflutter.Core



)であるという事実のためにNeedsUpdate()



好きNeedsUpdate()



が、グローバルflutter.Rerender()



メソッドも良いようです。 確かに、ウィジェットはすぐに再描画されるという誤った感覚を与えますが、そうではありません-次のフレーム更新で再描画され、メソッド呼び出しの頻度はレンダリング頻度よりもはるかに高くなる可能性がありますが、Flutterエンジンは既にこれを処理できるはずです







しかし、アイデアは、追加することなく必要な問題を解決しただけです。









さらに、APIははるかに明確で理解しやすい-カウンターを増やして(他のプログラムと同じように)、Flutterにウィジェットの再描画を依頼します。 これは、単にsetState



場合にはあまり明らかではないものです。これは、単に状態を設定するための特別な関数ではなく、関数(wtf?)を返す関数です。 繰り返しになりますが、言語とフレームワークに隠された魔法は、コードの理解と読み取りを非常に困難にします。







私たちの場合、同じ問題を解決しました。コードはよりシンプルで、2倍短くなりました。







他のウィジェットの状態ウィジェット



トピックの論理的な続きとして、「状態ウィジェット」がFlutterの別のウィジェットでどのように使用されるかを見てみましょう。







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





ここのMyHomePage



は「状態ウィジェット」(カウンターがある)であり、ビルド中にコンストラクターMyHomePage()



呼び出して作成します...待って、何ですか?







build()



は、ウィジェットを再描画するために呼び出されますが、1秒に何回も実行される可能性があります。 レンダリング中に毎回、特に状態を持つウィジェットを作成する必要があるのはなぜですか? 意味がありません







Flutterは、この初期化/状態管理をプログラマーから隠すために、 Widget



State



間のこの分離を使用することがわかります(もっと隠れたもの、もっと!)。 毎回新しいウィジェットを作成しますが、すでに作成されている場合は自動的に状態を見つけてウィジェットに添付します。 この魔法は目に見えないところで起こり、どのように機能するのか分かりません。コードを読む必要があります。







私は、人間工学で正当化することを可能な限りプログラマーに隠したり隠したりすることは、プログラミングの本当の悪だと考えています。 平均的な統計プログラマーは、この魔法がどのように機能するかを理解するためにFlutterコードを読まず、相互接続の方法と内容を理解する可能性は低いと確信しています。







Goバージョンでは、このような隠されたソーサリーは絶対に望まないでしょうし、たとえそれが少し根拠のないコードであっても、明示的で目に見える初期化を好むでしょう。 Dutterに対するFlutterのアプローチも実装できますが、魔法を最小限に抑えるためにGoが大好きです。フレームワークでも同じ哲学を見てみたいと思います。 したがって、ウィジェットツリーに状態を持つウィジェットのコードは、次のように記述します。







 // MyApp is our top application widget. type MyApp struct { flutter.Core homePage *MyHomePage } // NewMyApp instantiates a new MyApp widget func NewMyApp() *MyApp { app := &MyApp{} app.homePage = &MyHomePage{} return app } // Build renders the MyApp widget. Implements Widget interface. func (m *MyApp) Build(ctx flutter.BuildContext) flutter.Widget { return m.homePage } // MyHomePage is a home page widget. type MyHomePage struct { flutter.Core counter int } // Build renders the MyHomePage widget. Implements Widget interface. func (m *MyHomePage) Build(ctx flutter.BuildContext) flutter.Widget { return flutter.Scaffold() } // incrementCounter increments app's counter by one. func (m *MyHomePage) incrementCounter() { m.counter++ flutter.Rerender(m) }
      
      





このコードは、ウィジェットツリーからhomePage



を削除して別のものに置き換える場合、1つではなく3つの場所で削除する必要があるという点で、Dartバージョンを失いhomePage



。 しかし、その見返りとして、何が、どこで、どのように発生するのか、メモリがどこに割り当てられるのか、誰が誰を呼び出すのかなどの完全な画像を取得します。手のひらのコードは明確で読みやすいです。







ちなみに、FlutterにはStatefulBuilderのようなものもあります。これにより、さらに魔法が追加され、状態がオンザフライのウィジェットを作成できます。







DSL



それでは、楽しいことをしてみましょう。 Goでウィジェットツリーをどのように表現しますか? 簡潔で、きれいで、リファクタリングと変更が簡単で、ウィジェット(視覚的に近く、近くにあり記述内にある必要があるウィジェット)間の空間的な関係を記述し、同時に任意のイベントハンドラのようなコード。







Dartのオプションは非常に美しく雄弁であるように思えます。







 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)
      
      





これはテキストを追加しますが、パラメーターの意味を理解しようとして、クリックを保持し、コード内を移動します。







ウィジェットツリーの場合、読みやすさにおいて重要な役割を果たします。 上記と同じコードを、名前付きパラメーターなしで比較します。







 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), ), );
      
      





そうではありません。 そう? 理解するのが難しいだけではありません(各パラメーターの意味とそのタイプを覚えておく必要があり、これは重要な認知的負荷です)だけでなく、転送するパラメーターを自由に選択することもできません。 たとえば、マテリアルアプリケーションにFloatingActionButton



を使用したくない場合があります。そのため、パラメーターでFloatingActionButton



を指定しないでください。 名前付きパラメーターがない場合、可能なすべてのウィジェットを強制的に指定するか、リフレクションを使用してマジックを使用して、どのウィジェットが転送されたかを調べる必要があります。







また、Goには関数と名前付きパラメーターのオーバーロードがないため、これはGoにとって簡単なタスクではありません。







Goウィジェットツリー



バージョン1



Scaffoldオブジェクトを詳しく見てみましょう。これは、モバイルアプリケーションの便利なラッパーです。 appBar、drawe、home、bottomNavigationBar、floatingActionBarのプロパティがあり、これらはすべてウィジェットです。 ウィジェットツリーを作成するとき、実際には何らかの方法でこのオブジェクトを初期化し、前述のウィジェットプロパティを渡す必要があります。 さて、これはオブジェクトの通常の作成と初期化とあまり変わらない。







額のアプローチを試してみましょう:







 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



という言葉flutter



どこにでもあり、尋ねます。 非表示にするために(実際には、 flutter



ではなく、本質ではなくパッケージmaterial



に名前を付ける必要がありました)、名前のないパラメーターは完全に明白ではなく、これらのnil



はどこでも公然と混乱しています。







バージョン2



それでも、ほとんどのコードはflutter



パッケージのいずれかのタイプ/関数を使用するため、「ドットインポート」形式を使用してパッケージをネームスペースにインポートし、パッケージ名を「隠す」ことができます。







 import . "github.com/flutter/flutter"
      
      





flutter.Text



代わりに、 Text



だけを書くことができます。 これは通常、悪い習慣ですが、私たちはフレームワークで作業しており、このインポートは文字通りすべての行にあります。 私の練習から、これはまさにそのようなインポートが受け入れられる場合です-例えば、 GoConveyをテストするために素晴らしいフレームワークを使用するときのように。







コードがどのように見えるか見てみましょう:







 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-sと名前のないパラメーター....







バージョン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



少し戻って、何をしようとしているのか自問してみましょう。 Dartのアプローチをやみくもにコピーする必要はありません(これは素晴らしいボーナスですが、DartのFlutterに既に精通している人々から学ぶことは少なくなります)。 基本的に、新しいオブジェクトを作成してプロパティを割り当てるだけです。







この方法で試すことができますか?







 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
      
      





このアプローチは機能し、「名前付きパラメーター」の問題を解決しますが、ウィジェットツリーの全体像を混乱させます。 まず、ウィジェットを定義する順序が逆になります。ウィジェットが深くなるほど、ウィジェットを早く作成する必要があります。 第二に、コードの便利な視覚的構造を失いました。インデントを使用しても、ツリー内のウィジェットの深さがすぐにわかります。







ところで、このアプローチはGTKQtのようなUIフレームワークで長い間使用されてきました。 , , 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);
      
      





, - . , , , .







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



, . (proposal) — " " . , FloatingActionButtonParameters{...}



{...}



. :







 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



, . , , , , .







, , , -, – :







 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-, :









New...()



– , . , — " , , , , , , – " .







, , 5- 6- .









"hello, world" Flutter Go:







main.go







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





app.go:







 package hello import . "github.com/flutter/flutter" // MyApp is our top application widget. type MyApp struct { Core homePage *MyHomePage } // NewMyApp instantiates a new MyApp widget func NewMyApp() *MyApp { app := &MyApp{} app.homePage = &MyHomePage{} return app } // Build renders the MyApp widget. Implements Widget interface. func (m *MyApp) Build(ctx BuildContext) Widget { return m.homePage }
      
      





home_page.go:







 package hello import ( "fmt" . "github.com/flutter/flutter" ) // MyHomePage is a home page widget. type MyHomePage struct { Core counter int } // Build renders the MyHomePage widget. Implements Widget interface. 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, }), }, ), }) } // incrementCounter increments app's counter by one. func (m *MyHomePage) incrementCounter() { m.counter++ flutter.Rerender(m) }
      
      





!







おわりに



Vecty



, , Vecty . , , , , Vecty DOM/CSS/JS, Flutter , 120 . , Vecty , Flutter Go Vecty .







Flutter



– , . Flutter, .







Go



" Flutter Go?" "" , , , , , Flutter, , , "" . , Go .







, Go . . Go, , , -. – , , .







Go. – , .







Flutter



, Flutter , , . "/ " , Dart ( , , ). Dart, , (, ) DartVM V8, Flutter – Flutter -.







, . . , , 1.0 . , - .







game changer, Flutter , , .







UI – Flutter, .







参照資料






All Articles