本文的核心内容包含:

  • 数据逻辑解决
  • 布局中的逻辑解决
  • Flutter类型数据处理

一、数据逻辑解决

咱们接触的每一个Flutter界面,大多由布局和逻辑相干的代码组成。如Flutter初始工程的Counting Demo的代码:

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.headline4,            ),          ],        ),      ),      floatingActionButton: FloatingActionButton(        onPressed: _incrementCounter, // 事件回调        tooltip: 'Increment',        child: Icon(Icons.add),      ),    );  }}

如上,正文标记的就是页面中的数据以及数据处理逻辑局部。Fair的逻辑动态化是要实现这部分变量、办法等内容的脚本级动静解析以及DSL布局属性的绑定和后续用户操作事件办法的调用。

如何把Dart文件中的逻辑,变成可动静运行的变量和相干的操作呢?咱们在我的项目初期也进行了若干计划的探讨与Demo开发尝试。例如间接下发Dart文件,在纯Dart侧实现布局和逻辑的解析与数据绑定,此操作相似于要开发一个Dart Script引擎,开发成本较高。咱们最终确定:研发Dart2JS工具,把界面Dart文件的逻辑局部转换成JS脚本,而后利用原生的JSCore实现逻辑脚本的动静运行,由此咱们最终的构造如下。

咱们实现了,把一个@FairPatch注解标注过的界面文件(Widget),通过Fair-Compiler工具编译后生成DSL布局文件和JS逻辑文件。为什么咱们须要应用FairPatch注解,标注后生成?是因为咱们整体的设计理念是Flutter 原生界面和动静界面能够应用一个源文件来转换,动态化只是在紧急需要或者需要不确定的A/B测等长期场景应用,需要稳固之后,积淀下来的源文件能够持续跟版应用,最大化的放弃Flutter的性能。

Widget转换出的动静脚本,如下图所示。具体的原理请看“Fair下发产物–DSL生成原理与实现”和“Fair下发产物–Dart2JS生成原理与实现”。

在产物生成过程中,为了反对逻辑动态化,咱们须要转译的原始Dart语法减少了很多。我拿Demo例子中的_counter变量举例,它是一个根底int类型。应用在如下代码中:

Text('$_counter')

尽管看起来是语句简短,然而在生成Text Widget时,数据绑定的阶段须要做粗疏的辨别。如下是num转string再做参数传递的场景:

"className": "Text","pa": [  "#($_counter)"],

逻辑动态化,最外围的难点就是如何解决变量。Flutter中的变量,能够是数据变量也能够是办法变量。依据Demo中的场景,我列举一下(咱们依据数据绑定的须要,雷同解决形式的变量表达式咱们做了合并):

字符串强转根底变量

Text('$_counter') => #($_counter)

widget 传参变量

Text(widget.title) => #(widget.title)

办法变量(回调执行)

onPressed: _incrementCounter => @(incrementCounter)

这些Flutter代码中的变量和逻辑办法,通过Fair 编译工具的转译变成了在DSL文件中的标识,变量和办法逻辑最终都会在JS引擎中具体实现。当然在从布局DSL生成对应界面的时候,须要咱们实现数据绑定的模块。数据绑定过程就是依据下面提到的#() 、@()等不同表达式,实现Flutter 侧和 JS侧的变量解决需要,最终实现指标Widget的生成。

1.1 Dart和JS 双域变量映射

只从Dart侧去发动所需的变量值查问和对变量解决的办法调用,JS侧根本只作为数据值存储和数据逻辑相干的操作,工作示意图如下。

Fair中的MVVM依赖于Flutter原生模式,如上图所示,JS域的数据同步给Dart域,只须要在JS侧调用相熟的setState即可。当然这部分对应用Fair框架的开发者是无感知的,编译工具帮咱们实现了相干的转换。原生代码和生成的JS代码,比照如下:

JS:_incrementCounter: function _incrementCounter() {    const __thiz__ = this;    with (__thiz__) {        setState('#FairKey#', function dummy() {            _counter++;        });    }},
Dart:void _incrementCounter() {  setState(() {    _counter++;  });}

能够看到,除了JS简化拜访域的with和通信指标对象须要的FairKey,其余代码差异并不大。

1.2 生命周期

下面介绍了,双域的变量值如何映射。可能有同学就会问,Dart和JS的生命周期是怎么同步的呢?(Fair 提供的是StatefulWidget载体,所以此处只介绍StatefulWidget生命周期和JS生命周期的对应关系)

如上图所示,Fair 提供给JS域2个感知的生命周期的回调。onLoad是在JS加载实现,告诉JS侧进行变量初始化;onUnload是页面隐没,告诉JS侧进行资源回收。

1.3 最小渲染

Fair中对Widget属性的值变量都会生成对应的ValueNotifier对象,来实现部分的组件渲染。计算型变量,例如办法参数,咱们不做非凡解决。参加Widget层级治理的变量,咱们须要通过setState()来实现Reload全布局。

Sugar.ifEqualBool(_countCanMod2(),    // 管制Widgte布局的    trueValue: Image.asset('assets/image/logo.png'),    falseValue: Image.asset('assets/image/logo2.png')),

到这里咱们能够整体回顾一下,Fair架构的工作流,如下图所示:

阐明如下:

  1. Dart 界面源文件通过Fair 编译工具转化成布局DSL和逻辑JS 文件
  2. JS逻辑文件加载到JSCore,并实现JS域的初始化
  3. DSL布局文件通过解析模块,生产对应的界面Widget
  4. 在Widget的生成过程中,属性、事件等注册;值拜访等须要依赖5的通信管道
  5. 通过Flutter和JS模块的通信,实现JS域的值读取和办法解决

二、布局中的逻辑解决

2.1 布局中的逻辑解决

Fair把在布局中罕用的逻辑,进行了语法糖的封装。为什么采纳语法糖的形式,次要是心愿缩小Fair Compiler工具的转换老本。开发者也能够依据本人的须要进行扩大。举个例子,有如下一段逻辑:

condition == true ? widget1 : widget0

采纳语法糖封装后,(判断_count 是否为 2),如下:

Sugar.ifEqual(_count,2,         trueValue:Image.asset('assets/image/logo.png'),        falseValue: Image.asset('assets/image/logo2.png')),

此外,Fair布局中还反对ifRang、map、mapEach等语法糖。语法糖设计与实现,详见“Fair 逻辑语法糖设计与实现”。

2.2 布局中的子办法解决

在我的项目中,build内的布局代码往往会十分多,大部分开发者都会拆分成小的布局子办法。例如:

// 定义布局子办法Widget _titleWidget() {  return Text(getTitle());}// 组合布局...appBar: AppBar(  title: _titleWidget(),)...

下面的布局通过DSL转化后,失去的转化DSL JSON构造如下:

{  "className": "Scaffold",  "na": {    "appBar": {      "className": "AppBar",      "na": {        "title": "%(_titleWidget)"      }    },    "body": {...},  },  "methodMap": {    "createState": "%(_State)",    "_titleWidget": {      "className": "Text",      "pa": [        "%(getTitle)"      ]    }  }}

通过子布局办法在methodMap注册,在构建时实现整体拼装,来满足开发者对布局拆封的需要。到这里大家也会发现,Fair的设计是尽可能的把逻辑解决在Dart侧实现,缩小Flutter和JS的通信次数,进步整体动态化的性能。

三、Flutter类型变量的解决

可能大家会问,Fair是把Flutter外面的数据类型在JS侧都实现了一遍吗?答案是否定的,在JS侧咱们尽管筹备了一些Dart根底类和JS根底类的对应类型,然而并没有齐全笼罩。那开发者遇到Flutter内置类型,比方像ScrollController,咱们怎么解决呢?

Fair提供了原生模版加界面动静的模式,提供给开发者扩大。咱们用加载更多列表Demo 举例,原生模版如下:

class ListDelegate extends FairDelegate {  ScrollController _scrollController;  @override  Map<String, Function> bindFunction() {    var functions = super.bindFunction();    functions.addAll({      '_itemBuilder': _itemBuilder,      '_onRefresh': _onRefresh,    });    return functions;  }  @override  Map<String, PropertyValue> bindValue() {    var pros = super.bindValue();    pros.addAll({      '_scrollController': () => _scrollController,    });    return pros;  }  @override  void initState() {    // 监听滑动    _scrollController = ScrollController()      ..addListener(() {        //判断是否滑到底        if (_scrollController.position.pixels ==            _scrollController.position.maxScrollExtent) {          _onLoadMore();        }      });  }  @override  void dispose() {    super.dispose();    _scrollController.dispose();  }  void _onLoadMore() {    runtime?.invokeMethod(pageName, '_onLoadMore', null);  }  Future<void> _onRefresh() async {    await runtime?.invokeMethod(pageName, '_onRefresh', null);  }    // 构建item动静界面  Widget _itemBuilder(context, index) {    var result = runtime?.invokeMethodSync(pageName, '_onItemByIndex', [index]);    var value = jsonDecode(result);    var itemData = value['result'];    return FairWidget(      name: itemData['name'],      path: itemData['path'],      data: {'fairProps': jsonEncode({'item': itemData['props']})},    );  }}

这样设计的益处是,当须要不停的监听_scrollController.position的地位变动时,如果这个过程设计成Flutter不停的往JS侧发送地位偏移值,由JS侧实现阀值判断的话,对bridge的压力会十分大,性能也就十分的低。 

通过原生模版,再配合模版中应用的JS通信协议,能够齐全自定义JS侧的逻辑,此例的对应的JS如下:

GLOBAL['#FairKey#'] = (function (__initProps__) {    const __global__ = this;    return {        list: List(),         _scrollController: null,         listIsEmpty: function listIsEmpty() {            const __thiz__ = this;            with (__thiz__) {                return list == null || list.isEmpty;            }        }, _onLoadMore: async function onLoadMore() {            // 上拉加载更多解决        }, _onRefresh: async function _onRefresh() {            // 下拉加载更多解决        }, onLoad: function onLoad() {            // 界面逻辑数据初始化        }, onUnload: function onUnload() {            // 界面逻辑数据开释        }, _itemCount: function _itemCount() {            // 下拉加载更多解决        }, _onItemByIndex: function _onItemByIndex(index) {            // 获取单个列表卡片数据            const __thiz__ = this;            with (__thiz__) {                return list[index];            }        }    };})(convertObjectLiteralToSetOrMap(JSON.parse('#FairProps#')));

如上代码中的onItemByIndex、onRefresh、onLoadMore等办法名,只须要跟Dart侧的Delegate模版调用 一一对应起来就好。在模版中实现反对用户的自定义行为。

总结一下,Fair 逻辑动态化的设计和实现,对Fair如何实现Flutter逻辑动态化曾经有了肯定的理解。咱们从纯数据逻辑、布局中的逻辑、以及最初的模版+视图,来残缺的实现了逻辑动态化的反对。