Flutter高内聚组件怎么做?闲鱼打造开源高效方案!

fish_redux是闲鱼技术团队打造的开源flutter应用开发框架,旨在解决页面内组件间的高内聚、低耦合问题。开源地址:https://github.com/alibaba/fish-redux

从react_redux说起

redux对于前端的同学来说是一个比较熟悉的框架了,fish_redux借鉴了redux单项数据流思想。在flutter上说到redux,大家可能第一反应会类比到react上的react_redux。在react_redux中有个重要的概念——connect

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

简单得说,connect允许使用者从Redux store中获取数据并绑定到组件的props上,可以dispatch一个action去修改数据。

那么fish_redux中的connector是做什么的呢?为什么说connector解决了组件内聚的问题?我们应该如何理解它的设计呢?

connector in fish_redux

尽管都起到了连接的作用,但fish_redux与react_redux在抽象层面有很大的不同。

fish_redux本身是一个flutter上的应用框架,建立了自己的component体系,用来解决组件内的高内聚和组件间的低耦合。从connector角度来说,如何解决内聚问题,是设计中的重要考量。

fish_redux自己制造了 Component树, Component聚合了state和dispatch,每一个子 Component的state通过 connector从父 Component的state中筛选。如图所示:

可以看到,fish_redux的connector的主要作用把父子 Component关联起来,最重要的操作是filter。state从上之下是一个严谨的树形结构,它的结构复用了 Component的树形结构。类似一个漏斗形的数据管道,管理数据的分拆与组装。它表达了如何组装一个 Component

而对于react_redux来说,它主要的作用在于把react框架和redux绑定起来,重点在于如何让React component具有Redux的功能。

从图中可以看到,react_redux和React是平行的结构,经过 mapStateToProps后的state也不存在严谨的树形结构,即对于一个React component来说,它的state来自于Redux store而不是父component的state。从框架设计的角度来说,react_redux最重要的一个操作就是attach

源码分析

说完概念,我们从源码的角度来看看fish_redux中的connector是如何运作的,以fish_redux提供的example为例。

123456789101112class ToDoListPage extends Page<PageState, Map> {ToDoListPage()super(...dependencies: Dependencies(adapter: ToDoListAdapter(),slots: <String, Dependent>{'report': ReportConnector() + ReportComponent()}),...);}

在ToDoListPage的构造函数中,向父类构造传递了一个 Dependencies对象,在构造 Dependencies时,参数 slots中包含了名叫”report”的item,注意这个item的生成,是由一个 ReportConnector+ ReportComponent产生的。

从这里我们得出一个简单却非常重要的结论:

在fish_redux中,一个Dependent = connector + Component 。

Dependent代表页面拼装中的一个单元,它可以是一个 Component(通过buildComponent函数产生),也可以是一个 Adapter(由buildAdapter函数产生)。这样设计的好处是,对于View拼装操作来说, Dependent对外统一了API而不需要透出更多的细节。

根据上面我们得出的结论, connector用来把一个更小的 Component单元链接到一个更大的 ComponentAdapter上。这与我们之前的描述相符合。

connector到底是什么?

知道了 connector的基本作用,我们来看一下它到底链接了哪些东西以及如何链接。

先来看一下ReportConnector类的定义:

class ReportConnector extends ConnOp

ReportConnector继承了 ConnOp类,所有 connector的操作包括+操作,都来自于 ConnOp类。

set/get

既然是数据管道,就会有获取和放置。

set函数的入参很好得表达了 TP的意思,即把一个 P类型的 subState合并到 T类型的 state中。

再回头看 get函数,就很好理解了, get函数表达的就是如何从 T类型的 state中获取 P类型的 subStateDependent使用。

operator +

+操作符的重载是我们最初看到connector作用的地方,也是connector发挥作用的入口。

LogicComponentAdapter的父类,它表示页面组装元素的逻辑层,里面包含了 reducer/ effect/ higherEffect等与逻辑相关的元素以及它的组装过程。

operator +调用了 createDependent函数,接着会调用到 _Dependent类的构造函数,这里将 logicconnector放入 _Dependent内部,在后面fish_redux对 Component组装的过程中,connector会随着外部对 _Dependent中函数的调用发挥作用。

connector正式登场

铺垫了这么多,是该connector正式发挥作用的时候了。

get

我们以 Component为例,会调用到 _DependentbuildComponent函数:

1234Widget buildComponent(MixedStore store, Get getter) {final AbstractComponent component = logic;return component.buildComponent(store, () => connector.get(getter()));}

这里的 logic实际就是一个 Component对象,在调用 ComponentbuildComponent函数的时候,使用 get函数从一个大的父state中获取到当前 Component需要的数据集。接下去,这个变换后的子state将被用在例如 ViewBuilderRedcuer函数中。

这是connector在数据获取上的作用。

set

还是在 _Dependent类里面,看 createSubReducer函数:

1234SubReducer createSubReducer() {final Reducer reducer = logic.reducer;return reducer != null ? connector.subReducer(reducer) : null;}

首现从一个 Logic(这里实际上是一个 Component)对象中获取到外部设置进来的 reducer,接着调用 subReducer返回一个 SubReducer对象。 SubReducer是一个被wrap后的 Reducer。

subReducer的实现在 MutableConn中, ConnOp继承了 MutableConn类,也获得了这个能力。

12345678910111213141516SubReducer subReducer(Reducer reducer) {return (T state, Action action, bool isStateCopied) {final P props = get(state);if (props == null) {return state;}final P newProps = reducer(props, action);final bool hasChanged = newProps != props;final T copy = (hasChanged && !isStateCopied) ? _clone(state) : state;if (hasChanged) {set(copy, newProps);}return copy;};}

它首现通过 get函数得到一个变换后的数据集 props,接着调用原始的 reducer函数进行逻辑处理,这里有一个优化也是 SubReducer的作用,如果数据集在经过 reducer处理之后发生了变化,

并且state已经被copy过一次了(isStateCopied==true),就直接把 newProps通过 set函数更新到state中去。(这个优化可以防止多个子state发生变化的时候父state被拷贝多次)。

至此,connector在数据更新上的作用也体现出来了。

ReportConnector

最后,就好理解ReportConnector的实现了:

class ReportConnector extends ConnOp<PageState, ReportState> {  @override  ReportState get(PageState state) {    final ReportState reportState = ReportState();    reportState.total = state.toDos.length;    reportState.done =        state.toDos.where((ToDoState tds) => tds.isDone).toList().length;    return reportState;  }
 @override  void set(PageState state, ReportState subState) {}}

很明显的,在 get函数中, ReportStatePageState中获得了total/done字段。

总结

闲鱼客户端的详情页完全使用了fish_redux进行了重构,通过高内聚的 Component+connector形式,使得 Component可以被大量复用,很好得支持了5种类型的详情页。未来我们会基于fish_redux强大的扩展能力制作更多组件来满足不同业务对于框架的需求。 

Image placeholder
Sanker
未设置
  38人点赞

没有讨论,发表一下自己的看法吧

推荐文章
一个多业务、多状态、多操作的交易链路?闲鱼架构这样演进

前言双十一刚刚结束,成交额2684亿震惊全世界,每秒订单峰值达54.4W笔。在闲鱼2000万DAU,交易数额同样增长迅速的今天,我们如何保障交易链路的稳定与快速支撑业务?这篇文章从客户端开发的角度,介

基于JS的高性能Flutter动态化框架MXFlutter

导语:18年10月份,手机QQ看点团队尝试使用Flutter,做为iOS开发,一接触到Flutter就马上感受到,Flutter虽然强大,但不能像RN一样动态化是阻碍我们使用她的唯一障碍了。看Goog

走近科学,探究阿里闲鱼团队通过数据提升Flutter体验的真相

背景闲鱼客户端的Flutter页面已经服务上亿级用户,因此用户体验尤其重要,完善Flutter性能稳定性监控体系,以便及早发现线上性能问题,也可以作为用户体验提升的衡量标准。那么Flutter的性能到

闲鱼Flutter互动引擎系列——整体设计篇

什么是Candy引擎Candy引擎是闲鱼技术团队设计开发的一款:APP嵌入式的、轻量级的、易于开发、性能稳定的互动引擎;绘制系统高度融合Flutter体系,游戏场景和FlutterUI支持无缝混排;动

Flutter路由项目实战之fluro

github:https://github.com/zhengzhuan...关于flutter路由,在小项目中,就按照原生写法,但是在大型项目中,这样的我就不会进行推荐,我这里使用的fluro路由管

闲鱼如何高效承接并处理用户纠纷

背景闲鱼是一个基于C2C场景的闲置交易平台,每个用户既是买家也是卖家,在自由享受交易乐趣的同时也容易带来一些问题,如发一些侵权违规商品而不自知,发一些带情绪化言语对他人造成了伤害等,因此这也带来了一个

高并发下的接口幂等性解决方案!

一、背景我们实际系统中有很多操作,是不管做多少次,都应该产生一样的效果或返回一样的结果。例如:前端重复提交选中的数据,应该后台只产生对应这个数据的一个反应结果。我们发起一笔付款请求,应该只扣用户账户一

10分钟搞懂:亿级用户的分布式数据存储解决方案!

来源:IT进阶思维原创,转载请注明原出处内容提供:李智慧,前阿里巴巴技术专家,《大型网站技术架构》作者6月6日晚,林志玲与Akira公布婚讯、徐蔡坤祝福高考同学超常发挥,粉丝们百万的转发和点赞造成微博

多云时代下大规模数据库管理,该怎么做?

中国经济的高速发展是世界各国人们有目共睹的,随着数字经济时代的到来,金融数据库的管理,也随之面对一系列的新技术与挑战。云计算一直以来被认为是极具颠覆性的技术力量,随着云计算的应用普及,越来越为企业重视

.vue文件怎么使用组件?

.vue文件怎么使用组件?在vue中组件以.vue结尾,一个.vue文件就是一个组件。下面介绍下在组件中使用其他组件的方法。(相关课程推荐:Vue.js教程)1、首先创建一个组件MyTest.vue

跨平台开发的救星-让我们来了解一下flutter

第一次看文章的朋友可以关注我,会不定期发布Android面试内容、进阶专题等等。简介很多人已经用上了flutter,今天就来介绍一下Flutter架构Flutter框架分三层 Framework,En

揭秘!一个高准确率的Flutter埋点框架如何设计

背景用户行为埋点是用来记录用户在操作时的一系列行为,也是业务做判断的核心数据依据,如果缺失或者不准确将会给业务带来不可恢复的损失。闲鱼将业务代码从Native迁移到Flutter上过程中,发现原先Na

重磅|庖丁解牛之——Flutter for Web

Flutterfor Web在2018年冬的Flutter1.0伦敦发布会上,Flutter产品经理TimSneath通过一个滑动拼图的例子介绍了如何让Flutter运行在Web之上。这一当时代号Hu

Flutter环境搭建记录

1.下载安装包之后执行flutterdoctor报错:zsh:commandnotfound:flutter下载的是源码,去这里下载SDK 2.执行flutterdoctor,按照提示升级xcode、

gMIS吉密斯十年执念:Lower Costs较低成本Better Productivity较高效率

Hello2020!元旦快乐!今起揭开21世纪20年代的篇章.1.gMIS吉密斯十周年2010-2020,十年转眼已成历史,gMIS吉密斯——通用管理信息系统(generalManagementInf

看!闲鱼在ServiceMesh的探索和实践

背景在阿里服务端开发以Java为主的大背景下,其他异构语言业务如何调用现有Java服务,如何与集团中间件打通,就成为使用非Java语言团队必须要解决的首要问题。现状在ServiceMesh方案成熟之前

华为云存储All-Flash战略打造全新智能数据底座

华为全球产业展望报告显示,全球年数据增量将从2018年的32.5ZB快速增长到2025年的180ZB,但目前企业数据利用率只有10%,数据价值没有得到充分释放。面对海量数据爆炸式的增长和发掘数据内在价

天翼云视频云储存解决方案,高效解决云储存难题

随着科技发展,视频监控迈向深度智能时代,前端摄像机开始内置深度学习算法,可以对人脸、车辆等关键信息进行快速定位抓拍,有效解决漏抓误报问题,解决了传统智能视频分析技术人工选择特征准确率低、浅层学习模型无

揭秘!闲鱼拉新投放系统如何设计

背景闲鱼目前已经是国内最大的闲置物品交易平台。随着闲鱼体量的增长和用户规模不断扩大,闲鱼App上的一个普通banner抑或是feeds中的一张普通的卡片,每天都可能被数以千万计的人看到。为了更好地服务

css格式文件怎么打开

css格式文件怎么打开css文件可以使用系统自带的记事本打开,也可以使用专业的代码编辑器打开,如sublimetext、vscode、Dreamweaver。css文件是指css代码放到一个单独的文件

html找不到css文件怎么解决

html找不到css文件怎么解决如果你在引用css文件时,使用了错误的文件路径,就会导致引用失效。解决方法就是填写正确的css文件路径。下面我们学习下HTML填写路径的两种方法。(推荐学习:HTML视

nginx找不到js css文件怎么办

nginx找不到jscss文件怎么办js、css都算静态资源,之所以请求不到是因为nginx做代理后的虚拟路径和静态资源的请求路径不一致导致的。只需要更改root的配置就可以了。设置location如

.vue文件怎么请求api

.vue文件怎么请求api1、首先我们安装axios网络请求库cdtest//进入项目根目录 npmiaxios-S//执行安装2、在需要使用请求的vue组件内,引入axios 请求示例 //引

.vue文件怎么引入本地图片

.vue文件怎么引入本地图片.vue文件引入本地图片,需要先将本地图片复制到项目的src\assets目录中,否则不能使用。然后在template标签中,使用img标签引用图片即可。本地图片路径:在.

.vue文件怎么写css样式

.vue文件怎么写css样式.vue文件由结构——行为——样式三部分构成,分别对应template、script、style标签,css样式就写在style标签中。一、直接在Test.vue中写css