原创 王海君 58技术 2021-09-14 08:47
● 项目名称:Fair 2.0
● Github地址:https://github.com/wuba/fair
● 项目简介:Fair是为Flutter设计的动态化框架,可以通过Fair Compiler工具对Dart源文件的转化,使项目获得动态更新Widget的能力。Fair 2.0是为了解决 Fair 1.0版本的“逻辑动态化”能力不足。
语法糖(英语:Syntactic Sugar )是指 计算机语言 中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。Fair 语法糖 并不是为Dart语法提供易用性的接口,而是为了让Fair在布局和逻辑混编场景下构建更方便。
1 布局和逻辑混编场景下的思
如果需要转译如下的代码:
Widget getTitleWidget() { if (_count == 1) { return Text( 'Title 1', style: TextStyle(color: Colors.white), ); } else { return Text( 'Title Other', style: TextStyle(color: Colors.red), ); } }
我们需要怎么处理上面的代码呢?可行的方案应该有:
把上面的代码归属到逻辑模块,放到JS侧处理?
观察到此方法返回的是一个Widget,方法内部大部分都是布局相关,属于逻辑的只需要处理一个判断。是不是可以放到Dart侧 构建Widget Tree的时,做一下条件判断处理也可以呢?
方案1:
需要完整提供JS和Dart Widget的映射和JS侧的构建Widget的能力,这就变成了类似MXFlutter和Kraken的方案,区别只是他们需要开发者自己手写,Fair可以工具生成。
方案2:
需要告知布局解析引擎,构建时使用哪个分支。固定格式的判断、循环等,也都可以在Dart侧处理,相关的逻辑呈现就需要在DSL文件内。
经过思考,我们决定采用第2种方案,来解决布局和逻辑混编的场景问题,以及在Dart侧完成布局子方法的调用拼接。
方案3:
如果你有更好的设计,请联系我们。最终Fair中整个逻辑处理单元,如下图所示:
Fair逻辑处理单元中,语法糖支持布局中常用的分支和循环。Fair的设计区别于kraken和mxflutter这2种动态化方案,Fair的一个愿景是让同一份代码在Flutter原生和动态之间随意切换。在开发跟版本需求时,我们使用原生代码发布,以此持续保持Flutter的性能优势;而在热更新场景可以通过下发动态文件来达到动态更新Widget的目的。所以Fair在设计语法糖是需要考虑Flutter原生和动态2个场景下都可以正常运行。
本文重点介绍:
如何做到“双态“结果一致性
动态下的数据绑定和逻辑处理
Fair语法糖支持现状
(后续我们以Fair支持的List Map逻辑为例进行介绍, Fair example中语法糖内容也有示例)
2 “双态”一致性
由于Flutter原声态和动态的载体不一样,需要做“双态”一致性处理,确保2个状态下运行的结果一致,而且需要提供给开发者一致的编码接口。如下图所示,增加语法糖之后的运行流程:
2.1 原生态
在原生态场景下,我们只需要把List Map方法定义成一个静态方法,方法内支持目标语法的转换即可,这部分比较容易实现。例如我们把语法糖方法统一定义到Sugar类中,代码如下:
class Sugar { static List<T> map<T, E>(List<E> data, {T Function(E item) builder}) { return data.mapEach((index, item) => builder(item)) } }
2.2 动态
在动态场景下,Fair框架初始化时,会完成全局的语法糖模块的注册,在FairWidget载体运行时可以动态访问这些语法糖,再经过第2小节会介绍的数据绑定和逻辑处理,最终生成目标布局。接下来我们介绍一下语法糖的注册和动态访问。
语法糖方法注册
FairApp({ ....}) : placeholderBuilder = placeholder ?? _defaultHolder, super(key: key, child: child) { // 完成全局的模块的注册,其中就包括语法糖模块 setup(profile, delegate, generated, modules); }
// 语法糖动态注册 FairWidgetBinding provider = () { return { 'Sugar.map': (props) { var items = pa1(props); assert(items is List, 'failed to generate list of Sugar.map'); return ((items as List).asIteratorOf<Widget>() ?? items).toList(); }, };
如上代码中所示,setup是注册全局语法糖的入口;provider是提供的语法糖集,更多的语法糖方法可以在provider内定义。
语法糖方法访问
如何把List Item内容,展示到列表中的每个Cell中呢?下面我们给一个显示list数字的例子:
Dart侧,如下:
class _State extends State<MapPage> { var _list = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 100]; ... @override Widget build(BuildContext context) { return Scaffold( body: ListView( children: Sugar.map(_list, builder: (item) { return Container( child: Row(...), ); }), )); }}
转换后的DSL代码,如下:
{ ... "body": { "className": "ListView", "na": { "children": { "className": "Sugar.map", // 语法糖名称 "pa": [ "^(_list)" // 目标List ], "na": { "builder": { // 每一个list item展示的卡片布局 "className": "Container", "na": { "child": { "className": "Row", "na": { "children": [ { "className": "Expanded", "na": { "child": { "className": "Container", "na": { "child": { "className": "Row", "na": { "children": [...] } } } } } } } } }, "methodMap": {} }
当构建目标布局时,只需要从pa中取出list数据,迭代构建builder内布局DSL就好。
3 动态场景下的数据绑定和逻辑处理
在对语法糖模块进行设计时,我们只从JS域访问原始的数据,然后整个布局组合相关的工作都放在Flutter域中完成。List Map的循环迭代逻辑,跟Sugar.ifEqual这样的只需构建一次布局相比,Map、MapEach这样的循环逻辑,需要在布局解析时特殊处理。
如下解析时,处理迭代功能:
List<Widget> _buildSugarMap(
Function mapEach, Map map, Map methodMap, BuildContext context) {
var source = pa0(map);
var children = [];
// 根据脚本取出List在JS域的数据
if (source is String) {
var r = proxyMirror.evaluate(context, bound, source);
if (r.data != null) {
source = r.data;
}
}
if (!(source is List)) {
throw Exception('Sugar.mapEach has no valid source array');
}
//根据List 内容实现迭代解析
if (source != null && source is List) {
// Domain 内完成目标数据的内容替换
children = Domain(source).forEach(($, _) {
return convert(context, map['na']['builder'], methodMap, domain: $);
});
}
// 组装成整体目标布局
var params = {
'pa': [source, children]
};
return mapEach.call(params);
}
根据如上的代码以及注释内容,开发者只需要使用 Sugar.map(_list, builder: (item) {}) 语法糖就可以完成List内容和目标Item数据的绑定。但是目前Map和MapEach语法糖只能简单的处理固定名字的例如item和index的变量名称和内容,后续我们会跟整体一次处理的布局数据绑定逻辑处理保持一致。
4 Fair语法糖支持现状
目前Fair 框架默认内置了if、ifRange、Map等5个语法糖,后续会根据需要逐步扩展。当然开发者也可以提交常用的布局逻辑来扩展Fair语法糖集。
到这里关于Fair框架语法糖相关的内容就介绍完了。其实Fair语法糖模块的出现,归根到底就是Fair编译工具语法支持和开发者易用性方面的权衡的产物。语法糖会改变混编场景下的开发语法,随着解析工具能力和构建时数据处理能力的增强,这部分也会随之弱化。
谢谢大家!
交个朋友,帮我们点个star吧 🌟 😇:Github地址:https://github.com/wuba/fair