思想实验:随心所欲

最近,我发现了Flutter (Google的一个用于开发跨平台移动应用程序的新框架),甚至有机会向从未编程的人展示Flutter的基础知识。 Flutter本身是用Dart编写的-Dart是Chrome浏览器中出生的一种语言,后来逃到了控制台世界-这使我想到了“嗯,但Flutter完全可以用Go语言编写!”。


为什么不呢 Go和Dart都是由Google创建的,都使用了已编译的语言-围绕某些事件进行一些稍有不同的处理,Go将是实施Flutter这样的大规模项目的理想人选。 有人会说-Go中没有类,泛型和异常,因此不适合。


因此,让我们假设Flutter已经用Go编写了。 该代码的外观如何,并且通常会起作用?



Dart有什么问题?


自从这种语言在浏览器中替代JavaScript以来,我一直在关注它。 Dart已内置到Chrome浏览器中一段时间​​,希望它能取代JS。 疯狂的是伤心的2015年3月,以阅读, 支持达特已经从Chrome删除


飞镖本身很棒! 好吧,基本上,在使用JavaScript之后,任何语言都很棒,但是在说Go之后,Dart并不是那么漂亮。 但是还可以。 它具有所有可能的和难以想象的功能-类,泛型,异常,期货,异步等待,事件循环,JIT / AOT,垃圾收集器,函数重载-从编程语言理论中命名任何已知功能,在Dart中它将占很大比例概率。 Dart几乎对所有芯片都有特殊的语法-用于getter / setter的特殊语法,用于缩写构造函数的特殊语法,用于特殊语法的特殊语法,等等。


乍一看,这使Dart成为了以前已经使用任何编程语言进行编程的人所熟悉的,这很棒。 但是,在一个简单的“ Hello,world”示例中尝试解释所有这些丰富的特殊功能时,我发现与此相反,这使得很难掌握。


  • 该语言的所有“特殊”功能都令人困惑 -“称为构造函数的特殊方法”,“自动初始化的特殊语法”,“命名参数的特殊语法”等。
  • “隐藏”的所有内容都令人困惑 -“此函数从什么导入?它是隐藏的,查看您无法找到的代码”,“为什么在此类中有构造函数,但在此类中没有?构造函数在那里,但它是隐藏的”等等
  • 一切“模棱两可”的问题都令人困惑 -“因此要在这里创建带有或不带有名称的函数参数吗?”,“应该是const还是final?”,“在此处使用函数的常规语法或用”缩短箭头“”等。

原则上,这种三位一体-“特殊”,“隐藏”和“模棱两可”-可以抓住人们在编程语言中所谓的“魔术”的本质。 这些功能是为了简化代码编写而创建的,但实际上会使代码的阅读和理解复杂化。


而这恰恰是Go在其他语言上所处的根本不同立场,并且在激烈的防守中占据了重要位置。 Go是一种几乎没有魔法的语言-将其中的“隐藏”,“特殊”和“模棱两可”的数量减至最少。 但是Go有其缺点。


Go有什么问题?


由于我们正在谈论Flutter,而这是一个UI框架,因此我们将Go视为描述和使用UI的工具。 通常,UI框架是一个巨大的挑战,几乎总是需要专门的解决方案。 UI中最常见的方法之一是创建DSL (特定于域的语言),以特定于UI需求的库或框架的形式实现。 多数情况下,您会听到这样的观点:Go在客观上是DSL的一种糟糕语言。


从本质上讲,DSL意味着创建一种新的语言-术语和动词-开发人员可以对其进行操作。 它上的代码应清楚地描述图形界面及其组件的主要特征,应具有足够的灵活性以自由约束设计人员的想象力,同时应具有足够的刚性以按照某些规则对其进行限制。 例如,您应该能够将按钮放置在某个容器上,并将图标放在该按钮的正确位置,但是如果您尝试将按钮插入文本中,则编译器应该返回错误。


另外,用于描述UI的语言通常是声明性的-从而有机会以“我希望看到的东西”的形式描述接口,并让框架本身了解从哪些代码以及如何运行它。


某些语言最初是在具有此类任务的情况下开发的,但Go语言却没有。 在Go上编写Flutter似乎是另一项任务!


小田扑


如果您不熟悉Flutter,我强烈建议您在下个周末度过观看教育视频或阅读教程,其中有很多。 因为毫无疑问,Flutter在移动应用程序开发中颠覆了游戏规则。 而且,很可能不仅移动设备,而且已经有渲染器(就Flutter而言, 嵌入器)可以将Flutter应用程序作为本机dekstop应用程序Web应用程序启动


它很容易学习,很合乎逻辑,它带有一个巨大的Material Design设计精美的小部件库(并且不仅如此),它具有强大的社区和出色的调优功能(如果您喜欢在Go中然后在Flutter中轻松进行go build/run/test的工作,您将获得类似的体验)。


一年前,我需要编写一个小型移动应用程序(当然适用于iOS和Android),并且我意识到为两个平台开发高质量应用程序的复杂性太高了(该应用程序不是主要任务)-我不得不外包并为此付费。 实际上,即使对于拥有将近20年编程经验的人来说,编写一个简单但高质量的应用程序并在所有设备上工作都是一项不可能的任务。 对我而言,这一直都是胡说八道。


使用Flutter,我在下午3点重新编写了该应用程序,同时从头开始学习框架本身。 如果有人告诉我这可能要早一点,我不会相信。


上次发现新技术时,我上次看到生产率的类似提升是在5年前,当时我发现了Go。 那一刻改变了我的生活。


因此,我建议开始学习Flutter, 本教程非常好


Flutter上的“ Hello,World”


当您通过flutter create创建一个新的应用程序时,您将得到一个带有标题,文本,计数器和递增计数器的按钮的程序。



我认为这是一个很好的例子。 写在我们想象中的Flutter on Go上。 它几乎包含了您可以在其上测试该思想的框架的所有基本概念。 让我们看一下代码(这是一个文件):


 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的类中的特殊函数MyApp() ,而没有启动它,而是简单地调用了通常的显式函数而不是隐藏函数NewApp() 。 她做同样的事情,但是更清楚地解释和理解它的含义,起步方式和工作方式。


小部件类


在Flutter中,所有内容都由小部件组成。 在Flutter的Dart版本中,每个窗口小部件都实现为一个类,该类继承了Flutter中窗口小部件的特殊类。


Go中没有类,因此也没有类层次结构,因为世界不是面向对象的,甚至是更少的层次结构。 对于只熟悉面向类的OOP模型的程序员来说,这可能是一个启示,但实际上并非如此。 世界是概念,过程和相互作用的巨大交织图。 它的结构不是完美的,但也不是混乱的,尝试将其压缩到类层次结构中是使代码库不可读和笨拙的最可靠方法,而这正是目前大多数代码库所需要的。



我非常感谢Go,因为它的创建者费心思虑了这个无处不在的类概念,并在Go中实现了一个更简单,功能更强大的OOP概念,事实并非偶然,它更接近于OOP的创建者Alan Kay 的想法


在Go中,我们以特定类型的形式表示任何抽象-结构:


 type MyApp struct { // ... } 

在Flutter的Dart版本中, MyApp必须继承StatelessWidget并覆盖build方法。 这对于解决两个问题是必要的:


  1. 给我们的小部件( MyApp )一些特殊的属性/方法
  2. 使Flutter可以在构建/渲染过程中调用我们的代码

我不知道Flutter的内部原理,所以可以说第1项没有问题,我们只需要这样做即可。 Go为此提供了一个独特而明显的解决方案: 嵌入类型:


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

这段代码会将所有flutter.Core属性和方法添加到我们的MyApp类型中。 我称它为Core而不是Widget ,因为,首先,类型嵌入还没有使MyApp窗口小部件,其次,此名称在GopherJS Vecty框架中使用得很好(类似于React,仅用于Go)。 我将在稍后讨论Vecty和Flutter之间的相似性。


第二点-可以使用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() } 

我们可以在这里注意到一些区别:


  • 代码更加冗长BuildContextWidgetMaterialApp指向它们前面的导入。
  • 该代码没有那么少的依据-没有诸如extends Widget@override
  • Build()方法以大写字母开头,因为这意味着Go中该方法的“公开性”。 在Dart中,公开性取决于名称是否以下划线(_)开头。

因此,要在我们的Flutter on Go中制作小部件,我们需要嵌入flutter.Core类型并实现flutter.Widget接口。 我们弄清楚了,进一步挖掘。


条件


这是在Flutter中真正让我困惑的事情之一。 有两种不同的类StatelessWidgetStatefulWidget 。 对我而言,“无状态窗口小部件”是相同的窗口小部件,只是没有数据,状态,状态-为什么要提出一个新类? 但是,好的,我可以接受。


但是,此外-您不仅可以继承另一个类( 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类,该类继承State MyApp ...好的,您仍然可以以某种方式消化它。 但是为什么build()方法不是由State类定义的,而是由State类定义的? 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.Rerender()方法看起来也不错。 的确,给人一种错误的感觉,即小部件会立即立即重绘,但事实并非如此-它会在下一帧更新时重绘,并且方法调用频率可能比渲染频率高得多-但是我们的Flutter引擎应该已经处理了。


但想法是,我们无需添加即可解决必要的问题:


  • 新类型
  • 仿制药
  • 读/写状态的特殊规则
  • 特殊的新覆盖方法

另外,API更清晰,更易理解-只需增加计数器(就像在其他任何程序中一样),然后要求Flutter重绘该小部件即可。 如果我们仅调用setState ,这只是不太明显的事情-它不仅是用于设置状态的特殊函数,它是返回函数(wtf?)的函数,其中我们已经在对state进行了处理。 同样,语言和框架中的隐藏魔术使得很难理解和阅读代码。


在我们的案例中,我们解决了相同的问题,代码更简单,而且缩短了两倍。


状态小部件在其他小部件中


作为该主题的逻辑继续,让我们看一下Flutter中另一个小部件中如何使用“状态小部件”:


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

MyHomePage这里是一个“状态小部件”(它有一个计数器),我们在构建过程中通过调用构造函数MyHomePage()创建它。


调用build()来重绘窗口小部件,每秒可能多次。 为什么在渲染期间每次都要创建一个小部件,尤其是带有状态的小部件? 没有道理


事实证明 ,Flutter使用WidgetState之间的这种分隔来向程序员隐藏此初始化/状态管理(更多隐藏的东西,更多!)。 每次都会创建一个新的窗口小部件,但是状态(如果已经创建)会自动找到并附加到窗口小部件。 这种魔力是无形的,我不知道它是如何工作的-您需要阅读代码。


我认为在编程中尽可能多地向程序员隐藏和隐藏,以符合人体工程学的理由是一种真正的邪恶。 我敢肯定,一般的统计程序员不会阅读Flutter代码来了解这种魔术的工作原理,并且不太可能了解互连的方式和内容。


对于Go版本,我绝对不希望使用这种隐藏的巫术,并且宁愿使用显式且可见的初始化,即使这意味着稍微没有根据的代码也是如此。 Flutter使用Dart的方法也可以实现,但是我喜欢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) } 

这段代码丢失了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), ), ); 

不是那样 对不对 这不仅更加难以理解(您需要牢记每个参数的含义及其类型,这是很大的认知负担),而且也没有给我们自由选择要传递的参数。 例如,您可能不需要Material应用程序的FloatingActionButton ,因此您只需在参数中不指定它即可。 如果没有命名参数,我们要么必须强制指定所有可能的窗口小部件,要么借助反射魔术来找出传输了哪些窗口小部件。


而且由于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这个词无处不在,并要求。 要隐藏它(实际上,我必须给包装material命名,而不是flutter ,但不是本质),匿名参数是完全不明显的,并且这些nil在任何地方都令人困惑。


版本2


但是,由于大多数代码将使用flutter包中的一个或另一个类型/函数,因此我们可以使用“点导入”格式将包导入到我们的命名空间中,从而“隐藏”包名称:


 import . "github.com/flutter/flutter" 

现在我们可以只写Text而不是flutter.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的方法(尽管这将是一个不错的好处-教那些已经熟悉Flutter on Dart的人的知识很少)。 实际上,我们只是创建新对象并为其分配属性。


可以这样尝试吗?


 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


, – -. 例如:


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


参考文献


Source: https://habr.com/ru/post/zh-CN435960/


All Articles