Gedankenexperiment: Flattern unterwegs

Zuletzt entdeckte ich Flutter - ein neues Framework von Google für die Entwicklung plattformübergreifender mobiler Anwendungen - und hatte sogar die Möglichkeit, einer Person, die noch nie zuvor programmiert hatte, die Grundlagen von Flutter zu zeigen. Flutter selbst wurde in Dart geschrieben - einer Sprache, die im Chrome-Browser geboren und in die Konsolenwelt entkommen ist - und das ließ mich denken "hmm, aber Flutter könnte sehr gut in Go!" Geschrieben werden.


Warum nicht? Sowohl Go als auch Dart wurden von Google erstellt, beide haben kompilierte Sprachen geschrieben. Drehen Sie einige Ereignisse etwas anders um. Go wäre ein ausgezeichneter Kandidat für die Implementierung eines so großen Projekts wie Flutter. Jemand wird sagen - es gibt keine Klassen, Generika und Ausnahmen in Go, daher passt es nicht.


Stellen wir uns also vor, Flutter sei bereits in Go geschrieben. Wie wird der Code aussehen und im Allgemeinen wird er funktionieren?



Was ist los mit Dart?


Ich verfolge diese Sprache seit ihrer Einführung als Alternative zu JavaScript in Browsern. Dart wurde für einige Zeit in den Chrome-Browser integriert und hoffte, dass es JS ersetzen würde. Es war wahnsinnig traurig im März 2015 zu lesen, dass die Dart-Unterstützung von Chrome entfernt wurde .


Dart selbst ist großartig! Grundsätzlich ist nach JavaScript jede Sprache großartig, aber nach Go ist Dart nicht so schön. aber ganz ok Es hat alle denkbaren und undenkbaren Funktionen - Klassen, Generika, Ausnahmen, Futures, Async-Warten, Ereignisschleife, JIT / AOT, Garbage Collector, Funktionsüberladung - benennen Sie alle bekannten Funktionen aus der Theorie der Programmiersprachen und in Dart wird es mit einem hohen Anteil sein Wahrscheinlichkeiten. Dart hat eine spezielle Syntax für fast jeden Chip - eine spezielle Syntax für Getter / Setter, eine spezielle Syntax für abgekürzte Konstruktoren, eine spezielle Syntax für eine spezielle Syntax und vieles mehr.


Dies macht Dart auf den ersten Blick für Leute bekannt, die bereits in einer Programmiersprache programmiert haben, und das ist großartig. Als ich jedoch versuchte, all diese Fülle an Besonderheiten in einem einfachen Beispiel "Hallo Welt" zu erklären, stellte ich fest, dass dies im Gegenteil die Entwicklung erschwert.


  • Alle "speziellen" Funktionen der Sprache waren verwirrend - "eine spezielle Methode namens Konstruktor", "spezielle Syntax für die automatische Initialisierung", "spezielle Syntax für benannte Parameter" usw.
  • Alles, was "versteckt" war, war verwirrend - "von welchem ​​Import ist diese Funktion? Sie ist versteckt, wenn man den Code betrachtet, den man nicht herausfinden kann", "warum gibt es einen Konstruktor in dieser Klasse, aber nicht in dieser Klasse? Es ist da, aber es ist versteckt" und so weiter
  • alles "mehrdeutig" verwirrt - "also hier, um die Funktionsparameter mit oder ohne Namen zu erstellen?", "sollte es const oder final sein?", "hier die normale Syntax der Funktion verwenden oder" mit Pfeil verkürzt "usw."

Im Prinzip fängt diese Dreifaltigkeit - "speziell", "versteckt" und "mehrdeutig" - nicht schlecht die Essenz dessen ein, was die Leute in Programmiersprachen "Magie" nennen. Dies sind Funktionen, die erstellt wurden, um das Schreiben von Code zu vereinfachen, aber tatsächlich das Lesen und Verstehen erschweren.


Und genau in diesem Bereich nimmt Go eine grundlegend andere Position ein als andere Sprachen und hält die Verteidigung heftig. Go ist eine Sprache ohne Magie - die Menge an "versteckt", "speziell" und "mehrdeutig" wird minimiert. Aber Go hat seine Nachteile.


Was ist los mit Go?


Da es sich um Flutter handelt, ein UI-Framework, betrachten wir Go als Werkzeug zur Beschreibung und Arbeit mit der Benutzeroberfläche. Im Allgemeinen stellen UI-Frameworks eine enorme Herausforderung dar und erfordern fast immer spezielle Lösungen. Einer der häufigsten Ansätze in der Benutzeroberfläche ist die Erstellung von DSL - domänenspezifischen Sprachen -, die in Form von Bibliotheken oder Frameworks implementiert sind, die speziell auf die Anforderungen der Benutzeroberfläche zugeschnitten sind. Und am häufigsten hört man die Meinung, dass Go objektiv eine schlechte Sprache für DSL ist.


Im Wesentlichen bedeutet DSL, eine neue Sprache zu erstellen - Begriffe und Verben -, mit der der Entwickler arbeiten kann. Der darauf enthaltene Code sollte die Hauptmerkmale der grafischen Oberfläche und ihrer Komponenten klar beschreiben, flexibel genug sein, um der Vorstellungskraft des Designers freien Lauf zu lassen, und gleichzeitig starr genug sein, um sie gemäß bestimmten Regeln einzuschränken. Sie sollten beispielsweise in der Lage sein, die Schaltflächen in einem Container zu platzieren und das Symbol an der richtigen Stelle in dieser Schaltfläche zu platzieren. Der Compiler sollte jedoch einen Fehler zurückgeben, wenn Sie versuchen, die Schaltfläche beispielsweise in Text einzufügen.


Außerdem sind die Sprachen zur Beschreibung der Benutzeroberfläche häufig deklarativ. Sie bieten die Möglichkeit, die Benutzeroberfläche in Form von „Was ich sehen möchte“ zu beschreiben und das Framework selbst verstehen zu lassen, aus welchem ​​Code und wie es ausgeführt wird.


Einige Sprachen wurden ursprünglich mit solchen Aufgaben in Sicht entwickelt, Go jedoch nicht. Es scheint, dass das Schreiben von Flutter on Go eine weitere Aufgabe sein wird!


Oda Flutter


Wenn Sie mit Flutter nicht vertraut sind, empfehle ich dringend, das nächste Wochenende mit dem Anschauen von Lehrvideos oder dem Lesen von Tutorials zu verbringen, von denen es viele gibt. Denn Flutter kehrt zweifellos die Spielregeln bei der Entwicklung mobiler Anwendungen um. Und höchstwahrscheinlich nicht nur mobil - es gibt bereits Renderer (in Bezug auf Flutter, Einbettungen), mit denen Flutter-Anwendungen als native Dekstop-Anwendungen und als Webanwendungen gestartet werden können .


Es ist leicht zu erlernen, es ist logisch, es enthält eine riesige Bibliothek mit schönen Widgets zum Thema Materialdesign (und nicht nur), es hat eine großartige und große Community und eine hervorragende Abstimmung (wenn Sie die einfache Arbeit mit go build/run/test in Go mögen, dann in Flutter Sie werden eine ähnliche Erfahrung machen).


Vor einem Jahr musste ich eine kleine mobile Anwendung schreiben (natürlich für iOS und Android), und mir wurde klar, dass die Komplexität der Entwicklung einer hochwertigen Anwendung für beide Plattformen zu groß ist (die Anwendung war nicht die Hauptaufgabe) - ich musste sie auslagern und dafür bezahlen. Tatsächlich war das Schreiben einer einfachen, aber qualitativ hochwertigen Anwendung für alle Geräte selbst für eine Person mit fast 20 Jahren Programmiererfahrung eine unmögliche Aufgabe. Und es war schon immer Unsinn für mich.


Mit Flutter habe ich diese Anwendung um 15 Uhr neu geschrieben und dabei das Framework selbst von Grund auf gelernt. Wenn mir jemand sagen würde, dass dies etwas früher sein könnte, würde ich es nicht glauben.


Das letzte Mal, dass ich mit der Entdeckung neuer Technologien einen ähnlichen Produktivitätsschub sah, war vor 5 Jahren, als ich Go entdeckte. Dieser Moment hat mein Leben verändert.


Ich empfehle daher, Flutter zu lernen, und dieses Tutorial ist sehr gut .


"Hallo Welt" auf Flutter


Wenn Sie eine neue Anwendung durch flutter create , erhalten Sie ein solches Programm mit einem Titel, Text, Zähler und einer Schaltfläche, die den Zähler erhöht.



Ich denke, das ist ein großartiges Beispiel. um es auf unserem imaginären Flutter on Go zu schreiben. Es enthält fast alle Grundkonzepte des Frameworks, auf denen Sie die Idee testen können. Schauen wir uns den Code an (dies ist eine Datei):


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

Lassen Sie uns den Code in Teilen analysieren, analysieren, was und wie er auf Go passt, und einen Blick auf die verschiedenen Optionen werfen, die wir haben.


Wir übersetzen den Code auf Go


Der Anfang ist einfach und unkompliziert - importieren Sie die Abhängigkeit und starten Sie die Funktion main() . Nichts kompliziertes oder interessantes hier, die Änderung ist fast syntaktisch:


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

Der einzige Unterschied besteht darin, dass MyApp() anstelle von MyApp() - einer Funktion, die ein Konstruktor ist, eine spezielle Funktion, die in einer Klasse namens MyApp verborgen ist - einfach die übliche explizite und nicht versteckte Funktion NewApp() . Sie macht das Gleiche, aber es ist viel klarer zu erklären und zu verstehen, was es ist, wie es beginnt und wie es funktioniert.


Widget-Klassen


In Flutter besteht alles aus Widgets. In der Dart-Version von Flutter ist jedes Widget als Klasse implementiert, die spezielle Klassen für Widgets von Flutter erbt.


In Go gibt es keine Klassen und daher keine Klassenhierarchie, da die Welt nicht objektorientiert und noch weniger hierarchisch ist. Für Programmierer, die nur mit dem klassenorientierten OOP-Modell vertraut sind, mag dies eine Offenbarung sein, ist es aber wirklich nicht. Die Welt ist ein riesiger, verwobener Graph von Konzepten, Prozessen und Interaktionen. Es ist nicht perfekt strukturiert, aber auch nicht chaotisch, und der Versuch, es in die Klassenhierarchie zu drücken, ist der zuverlässigste Weg, um die Codebasis unlesbar und ungeschickt zu machen - genau das, was die meisten Codebasen derzeit sind.



Ich schätze Go sehr, weil seine Schöpfer sich die Mühe gemacht haben, dieses allgegenwärtige Konzept von Klassen zu überdenken und in Go ein viel einfacheres und leistungsfähigeres Konzept von OOP implementiert haben, das sich nicht zufällig als näher an dem herausstellte, was der Schöpfer von OOP, Alan Kay, vorhatte .


In Go repräsentieren wir jede Abstraktion in Form einer bestimmten Typstruktur:


 type MyApp struct { // ... } 

In der Dart-Version von Flutter muss MyApp StatelessWidget erben und die build Methode überschreiben. Dies ist notwendig, um zwei Probleme zu lösen:


  1. Geben Sie unserem Widget ( MyApp ) einige spezielle Eigenschaften / Methoden
  2. Aktivieren Sie Flutter, um unseren Code im Build / Render-Prozess aufzurufen

Ich kenne die Interna von Flutter nicht, also nehmen wir an, dass Artikel Nummer 1 nicht in Frage kommt und wir es einfach tun müssen. Go hat dafür eine einzigartige und offensichtliche Lösung: Einbetten von Typen:


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

Dieser Code fügt alle flutter.Core Eigenschaften und -Methoden zu unserem MyApp Typ hinzu. Ich habe es Core anstelle von Widget , weil erstens das Einbetten von Typen unsere MyApp noch nicht zu MyApp Widget macht, und zweitens wird dieser Name im GopherJS Vecty-Framework sehr gut verwendet (so etwas wie React, nur für Go). Ich werde etwas später auf das Thema der Ähnlichkeit zwischen Vecty und Flutter eingehen.


Der zweite Punkt - die Implementierung der build() -Methode, die die Flutter-Engine verwenden kann - wird in Go ebenfalls einfach und eindeutig gelöst. Wir müssen nur eine Methode mit einer bestimmten Signatur hinzufügen, die eine bestimmte Schnittstelle erfüllt, die irgendwo in unserer fiktiven Flutter-Bibliothek auf Go definiert ist:


flutter.go:


 type Widget interface { Build(ctx BuildContext) Widget } 

Und jetzt unser 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() } 

Wir können hier einige Unterschiede feststellen:


  • Der Code ist etwas ausführlicher - BuildContext , Widget und MaterialApp zeigen auf flutter Importe vor ihnen.
  • Der Code ist etwas weniger unbegründet - es gibt keine Wörter wie @override extends Widget oder @override
  • Die Build() -Methode beginnt mit einem Großbuchstaben, da dies die "Publizität" der Methode in Go bedeutet. In Dart wird die Werbung dadurch bestimmt, ob der Name mit einem Unterstrich (_) beginnt oder nicht.

Um ein Widget in unserem Flutter on Go zu flutter.Core , müssen wir das flutter.Core Typ einbetten und die flutter.Widget Oberfläche implementieren. Wir haben es herausgefunden und weiter gegraben.


Zustand


Dies war eines der Dinge, die mich in Flutter wirklich verwirrten. Es gibt zwei verschiedene Klassen - StatelessWidget und StatefulWidget . Was mich betrifft, ist ein "zustandsloses Widget" dasselbe Widget, nur ohne, hmm, Daten, Status - warum sollte man sich eine neue Klasse einfallen lassen? Aber okay, ich kann damit leben.


Aber weiter - mehr noch, Sie können nicht einfach eine andere Klasse ( StatefulWidget ) erben, sondern müssen eine solche Magie schreiben (die IDE wird es für Sie tun, aber nicht den Punkt):


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

Hmm, mal sehen was hier passiert.


Grundsätzlich lautet die Aufgabe: Fügen Sie dem Widget einen Status hinzu - in unserem Fall den Zähler - und teilen Sie der Flutter-Engine mit, wann wir den Status geändert haben, um das Widget neu zu zeichnen. Dies ist die tatsächliche Komplexität des Problems (wesentliche Komplexität in Brooks-Begriffen).


Alles andere ist zufällige Komplexität. Flutter on Dart wartet mit einer neuen State Klasse auf, die Generika verwendet und ein Widget als Typparameter verwendet. Als nächstes wird die _MyHomePageState Klasse erstellt, die State MyApp erbt ... okay, Sie können es trotzdem irgendwie verdauen. Aber warum wird die build() -Methode von der State-Klasse definiert und nicht von der Klasse, die das Widget ist? Brrr ....


Die Antwort auf diese Frage finden Sie in den Flutter-FAQ. Die kurze Antwort wird hier ausreichend detailliert behandelt, um eine bestimmte Fehlerklasse beim Erben von StatefulWidget zu vermeiden. Mit anderen Worten, dies ist eine Problemumgehung, um das Problem des klassenorientierten OOP-Designs zu lösen. Schick.


Wie würden wir das in Go machen?


Erstens würde ich persönlich es vorziehen, keine separate Einheit für den "Staat" - State - zu schaffen. Schließlich haben wir in jedem bestimmten Typ bereits einen Status - dies sind nur Felder der Struktur. Die Sprache hat uns diese Essenz sozusagen schon gegeben. Das Erstellen einer anderen ähnlichen Entität verwirrt den Programmierer nur.


Die Herausforderung besteht natürlich darin, Flutter die Möglichkeit zu geben, auf Zustandsänderungen zu reagieren (dies ist schließlich die Essenz der reaktiven Programmierung). Und wenn wir den Entwickler „bitten“ können, eine spezielle Funktion ( setState() ) zu verwenden, können wir auf die gleiche Weise eine spezielle Funktion verwenden, um der Engine mitzuteilen, wann neu gezeichnet werden soll und wann nicht. Am Ende müssen nicht alle Statusänderungen neu gezeichnet werden, und hier haben wir noch mehr Kontrolle:


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

Sie können mit verschiedenen Benennungsoptionen NeedsUpdate() - ich mag NeedsUpdate() wegen seiner Direktheit und der Tatsache, dass dies eine Widget-Eigenschaft ist (von flutter.Core ), aber die globale flutter.Rerender() -Methode sieht auch gut aus. Es stimmt, es gibt einen falschen Eindruck, dass das Widget sofort neu gezeichnet wird, aber es ist nicht so - es wird beim nächsten Frame-Update neu gezeichnet und die Häufigkeit von Methodenaufrufen kann viel höher sein als die Renderfrequenz -, aber unsere Flutter-Engine sollte bereits in der Lage sein, damit umzugehen.


Aber die Idee ist, dass wir gerade das notwendige Problem gelöst haben, ohne hinzuzufügen:


  • neuer Typ
  • Generika
  • Sonderregeln für den Lese- / Schreibzustand
  • spezielle neue überschriebene Methoden

Außerdem ist die API viel klarer und verständlicher - erhöhen Sie einfach den Zähler (wie in jedem anderen Programm) und bitten Sie Flutter, das Widget neu zu zeichnen. Dies ist nur etwas, was nicht sehr offensichtlich ist, wenn wir nur setState - dies ist nicht nur eine spezielle Funktion zum Festlegen des Status, sondern eine Funktion, die eine Funktion (wtf?) Zurückgibt, in der wir bereits etwas mit state tun. Wiederum macht es versteckte Magie in Sprachen und Frameworks sehr schwierig, Code zu verstehen und zu lesen.


In unserem Fall haben wir das gleiche Problem gelöst, der Code ist einfacher und zweimal kürzer.


Geben Sie Widgets in anderen Widgets an


Schauen wir uns als logische Fortsetzung des Themas an, wie das „Status-Widget“ in einem anderen Widget in Flutter verwendet wird:


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

MyHomePage hier ein "Status-Widget" (es hat einen Zähler), und wir erstellen es, indem wir den Konstruktor MyHomePage() während des MyHomePage() aufrufen ... Warten Sie, was?


build() wird aufgerufen, um das Widget möglicherweise mehrmals pro Sekunde neu zu zeichnen. Warum sollten wir beim Rendern jedes Mal ein Widget erstellen, insbesondere mit einem Status? Es macht keinen Sinn.


Es stellt sich heraus, dass Flutter diese Trennung zwischen Widget und Status verwendet, um diese Initialisierung / Statusverwaltung vor dem Programmierer zu verbergen (mehr versteckte Dinge, mehr!). Es wird jedes Mal ein neues Widget erstellt, aber der Status, falls er bereits erstellt wurde, wird automatisch gefunden und an das Widget angehängt. Diese Magie passiert unsichtbar und ich habe keine Ahnung, wie es funktioniert - Sie müssen den Code lesen.


Ich halte es beim Programmieren für ein echtes Übel, sich so weit wie möglich vor dem Programmierer zu verstecken und dies mit Ergonomie zu rechtfertigen. Ich bin sicher, dass der durchschnittliche statistische Programmierer Flutter-Code nicht lesen wird, um zu verstehen, wie diese Magie funktioniert, und es ist unwahrscheinlich, dass er versteht, wie und was miteinander verbunden ist.


Für die Go-Version würde ich definitiv keine solche versteckte Zauberei wollen und würde eine explizite und sichtbare Initialisierung bevorzugen, selbst wenn dies einen etwas unbegründeteren Code bedeutet. Flatters Ansatz für Dart kann ebenfalls implementiert werden, aber ich liebe Go, um Magie zu minimieren, und ich würde gerne die gleiche Philosophie in Frameworks sehen. Daher würde ich meinen Code für Widgets mit Status wie folgt in den Widget-Baum schreiben:


 // 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) } 

Dieser Code verliert die Dart-Version, da ich, wenn ich homePage aus dem Widget-Baum entfernen und durch etwas anderes ersetzen möchte, es an drei statt an einer Stelle entfernen muss. Im Gegenzug erhalten wir ein vollständiges Bild davon, was, wo und wie es passiert, wo der Speicher zugewiesen wird, wer wen anruft usw. Der Code in Ihrer Handfläche ist klar und leicht zu lesen.


Flutter hat übrigens auch so etwas wie StatefulBuilder , das noch mehr Magie hinzufügt und es Ihnen ermöglicht, Widgets mit State on the Fly zu erstellen.


DSL


Nehmen wir jetzt den lustigen Teil an. Wie werden wir den Widget-Baum auf Go darstellen? Wir möchten, dass es präzise, ​​sauber, einfach umzugestalten und zu ändern ist, die räumlichen Beziehungen zwischen Widgets beschreibt (Widgets, die sich visuell in der Nähe befinden, sich in der Nähe befinden müssen und in der Beschreibung enthalten sind) und gleichzeitig flexibel genug ist, um willkürlich zu beschreiben Code wie Ereignishandler.


Es scheint mir, dass die Option auf Dart sehr schön und beredt ist:


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

Jedes Widget verfügt über einen Konstruktor, der optionale Parameter akzeptiert. Was den Datensatz hier wirklich schön macht, sind die benannten Parameter der Funktionen .


Benannte Parameter


Falls Sie mit diesem Begriff nicht vertraut sind, werden die Parameter der Funktion in vielen Sprachen als "positionell" bezeichnet, da ihre Position für die Funktion von Bedeutung ist:


 Foo(arg1, arg2, arg3) 

und im Fall von benannten Parametern wird alles durch ihren Namen im Aufruf entschieden:


 Foo(name: arg1, description: arg2, size: arg3) 

Dies fügt Text hinzu, speichert jedoch Klicks und bewegt sich im Code, um zu verstehen, was die Parameter bedeuten.


Im Fall des Widget-Baums spielen sie eine Schlüsselrolle für die Lesbarkeit. Vergleichen Sie den gleichen Code wie oben, jedoch ohne benannte Parameter:


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

Nicht das. richtig? Es ist nicht nur schwieriger zu verstehen (Sie müssen bedenken, was jeder Parameter bedeutet und welchen Typ er hat, und dies ist eine erhebliche kognitive Belastung), sondern gibt uns auch keine Freiheit bei der Auswahl der Parameter, die wir übertragen möchten. Beispielsweise möchten Sie möglicherweise keinen FloatingActionButton für Ihre Materialanwendung, sodass Sie ihn einfach nicht in den Parametern angeben. Ohne benannte Parameter müssen wir entweder die Angabe aller möglichen Widgets erzwingen oder mit Reflexion auf Magie zurückgreifen, um herauszufinden, welche Widgets übertragen wurden.


Und da es in Go keine Überlastung von Funktionen und benannten Parametern gibt, wird dies für Go keine leichte Aufgabe sein.


Gehen Sie zum Widget-Baum


Version 1


Schauen wir uns das Scaffold- Objekt genauer an, das ein praktischer Wrapper für eine mobile Anwendung ist. Es hat mehrere Eigenschaften - AppBar, Drawe, Home, BottomNavigationBar, FloatingActionBar - und dies sind alles Widgets. Beim Erstellen eines Widget-Baums müssen wir dieses Objekt tatsächlich irgendwie initialisieren und ihm die oben genannten Widget-Eigenschaften übergeben. Nun, dies unterscheidet sich nicht allzu sehr von der üblichen Erstellung und Initialisierung von Objekten.


Versuchen wir den Stirnansatz:


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

Auf jeden Fall nicht der schönste UI-Code. Das Wort flutter überall und fragt. Um es zu verbergen (eigentlich musste ich das Verpackungsmaterial benennen, nicht flutter , aber nicht die Essenz), sind die anonymen Parameter völlig offensichtlich, und diese Nullen sind überall offen verwirrend.


Version 2


Da der größte Teil des Codes jedoch den einen oder anderen Typ / die andere Funktion aus dem flutter , können wir das Paket mithilfe des "Punktimport" -Formats in unseren Namespace importieren und dadurch den Paketnamen "verbergen":


 import . "github.com/flutter/flutter" 

Jetzt anstatt zu flutter.Text . Text können wir nur Text schreiben. Dies ist normalerweise eine schlechte Praxis, aber wir arbeiten mit dem Framework, und dieser Import wird buchstäblich in jeder Zeile erfolgen. Aus meiner Praxis ist dies genau der Fall, für den ein solcher Import akzeptabel ist - zum Beispiel, wenn das wunderbare Framework zum Testen von GoConvey verwendet wird .


Mal sehen, wie der Code aussehen wird:


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

Schon besser, aber diese Nullen und unbenannten Parameter ....


Version 3


Mal sehen, wie der Code aussehen wird, wenn wir Reflection (die Möglichkeit, den Code zu überprüfen, während das Programm ausgeführt wird) verwenden, um die übergebenen Parameter zu analysieren. Dieser Ansatz wird in mehreren frühen HTTP-Frameworks unter Go (z. B. Martini ) verwendet und wird als sehr schlechte Vorgehensweise angesehen. Er ist unsicher, verliert den Komfort des Typsystems, ist relativ langsam und fügt dem Code Magie hinzu. Aus Versuchsgründen können Sie jedoch Folgendes versuchen:


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

Nicht schlecht, und es sieht aus wie die Originalversion von Dart, aber das Fehlen benannter Parameter tut dem Auge immer noch weh.


Version 4


Lassen Sie uns einen Schritt zurücktreten und uns fragen, was wir versuchen zu tun. Wir müssen den Dart-Ansatz nicht blind kopieren (obwohl dies ein schöner Bonus ist - es gibt weniger, um Leute zu unterrichten, die bereits mit Flutter on Dart vertraut sind). Tatsächlich erstellen wir einfach neue Objekte und weisen ihnen Eigenschaften zu.


Kannst du das so versuchen?


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

, - . , , , .


5


, – -. Zum Beispiel:


 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) 

oder


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

!


Fazit


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


Referenzen


Source: https://habr.com/ru/post/de435960/


All Articles