Fish Redux中的Dispatch是怎么实现的?

前言

开源地址:https://github.com/alibaba/fish-redux

我们在使用fish-redux构建应用的时候,界面代码(view)和事件的处理逻辑(reducer,effect)是完全解耦的,界面需要处理事件的时候将action分发给对应的事件处理逻辑去进行处理,而这个分发的过程就是下面要讲的dispatch, 通过本篇的内容,你可以更深刻的理解一个action是如何一步步去进行分发的。
从example开始

为了更好的理解action的dispatch过程,我们就先以todolistpage中一条todo条目的勾选事件为例,来看点击后事件的传递过程,通过断点debug我们很容易就能够发现点击时候发生的一切,具体过程如下:

  1. 用户点击勾选框,GestureDetector的onTap会被回调
  2. 通过buildView传入的dispatch函数对doneAction进行分发,发现todo_component的effect中无法处理此doneAction,所以将其交给pageStore的dispatch继续进行分发
  3. pageStore的dispatch会将action交给reducer进行处理,故doneAction对应的_markDone会被执行,对state进行clone,并修改clone后的state的状态,然后将这个全新的state返回
  4. 然后pageStore的dispatch会通知所有的listeners,其中负责界面重绘的_viewUpdater发现state发生变化,通知界面进行重绘更新

Dispatch实现分析

Dispatch在fish-redux中的定义如下

typedef Dispatch = void Function(Action action);

本质上就是一个action的处理函数,接受一个action,然后对action进行分发。

下面我门通过源码来进行详细的分析。

01    component中的dispatch

buildView函数传入的dispatch是对应的component的mainCtx中的dispatch, _mainCtx和componet的关系如下 component->ComponentWidget->ComponentState->_mainCtx->_dispatch而 _mainCtx的初始化则是通过componet的createContext方法来创建的,顺着方法下去我们看到了dispatch的初始化

// redux_component/context.dart DefaultContext初始化方法
 DefaultContext({    @required this.factors,    @required this.store,    @required BuildContext buildContext,    @required this.getState,  })  : assert(factors != null),        assert(store != null),        assert(buildContext != null),        assert(getState != null),        _buildContext = buildContext {    final OnAction onAction = factors.createHandlerOnAction(this);
   /// create Dispatch    _dispatch = factors.createDispatch(onAction, this, store.dispatch);
   /// Register inter-component broadcast    _onBroadcast =        factors.createHandlerOnBroadcast(onAction, this, store.dispatch);    registerOnDisposed(store.registerReceiver(_onBroadcast));  }

context中的dispatch是通过factors来进行创建的,factors其实就是当前component,factors创建dispatch的时候传入了onAction函数,以及context自己和store的dispatch。onAction主要是进行Effect处理。这边还可以看到,进行context初始化的最后,还将自己的onAction包装注册到store的广播中去,这样就可以接收到别人发出的action广播。

Component继承自Logic

// redux_component/logic.dart
 @override  Dispatch createDispatch(      OnAction onAction, Context<T> ctx, Dispatch parentDispatch) {    Dispatch dispatch = (Action action) {      throw Exception(          'Dispatching while appending your effect & onError to dispatch is not allowed.');    };
   /// attach to store.dispatch    dispatch = _applyOnAction<T>(onAction, ctx)(      dispatch: (Action action) => dispatch(action),      getState: () => ctx.state,    )(parentDispatch);    return dispatch;  }
   static Middleware<T> _applyOnAction<T>(OnAction onAction, Context<T> ctx) {    return ({Dispatch dispatch, Get<T> getState}) {      return (Dispatch next) {        return (Action action) {          final Object result = onAction?.call(action);          if (result != null && result != false) {            return;          }
         //skip-lifecycle-actions          if (action.type is Lifecycle) {            return;          }

         if (!shouldBeInterruptedBeforeReducer(action)) {            ctx.pageBroadcast(action);          }
         next(action);        };      };    };  }}

上面分发的逻辑大概可以通过上图来表示

  1. 通过onAction将action交给component对应的effect进行处理
  2. 当effect无法处理此action,且此action非lifecycle-actions,且不需中断则广播给当前Page的其余所有effects
  3. 最后就是继续将action分发给store的dispatch(parentDispatch传入的其实就是store.dispatch)

02   store中的dispatch

从store的创建代码我们可以看到store的dispatch的具体逻辑

// redux/create_store.dart
 final Dispatch dispatch = (Action action) {    _throwIfNot(action != null, 'Expected the action to be non-null value.');    _throwIfNot(        action.type != null, 'Expected the action.type to be non-null value.');    _throwIfNot(!isDispatching, 'Reducers may not dispatch actions.');
   try {      isDispatching = true;      state = reducer(state, action);    } finally {      isDispatching = false;    }
   final List<_VoidCallback> _notifyListeners = listeners.toList(      growable: false,    );    for (_VoidCallback listener in _notifyListeners) {      listener();    }
   notifyController.add(state);  };

store的dispatch过程比较简单,主要就是进行reducer的调用,处理完成后通知监听者。

03   middleware

Page继承自Component,增加了middleware机制,fish-redux的redux部分本身其实就对middleware做了支持,可以通过StoreEnhancer的方式将middlewares进行组装,合并到Store的dispatch函数中。

middleware机制可以允许我们通过中间件的方式对redux的state做AOP处理,比如fish-redux自带的logMiddleware,可以对state的变化进行log,分别打印出state变化前和变化后的值。

当Page配置了middleware之后,在创建pageStore的过程中会将配置的middleware传入,传入之后会对store的dispath进行增强加工,将middleware的处理函数串联到dispatch中。

// redux_component/component.dart
 Widget buildPage(P param) {    return wrapper(_PageWidget<T>(      component: this,      storeBuilder: () => createPageStore<T>(            initState(param),            reducer,            applyMiddleware<T>(buildMiddleware(middleware)),          ),    ));  }
// redux_component/page_store.dart
PageStore<T> createPageStore<T>(T preloadedState, Reducer<T> reducer,        [StoreEnhancer<T> enhancer]) =>    _PageStore<T>(createStore(preloadedState, reducer, enhancer));
// redux/create_store.dart
Store<T> createStore<T>(T preloadedState, Reducer<T> reducer,        [StoreEnhancer<T> enhancer]) =>    enhancer != null        ? enhancer(_createStore)(preloadedState, reducer)        : _createStore(preloadedState, reducer);

所以这里可以看到,当传入enhancer时,createStore的工作被enhancer代理了,会返回一个经过enhancer处理过的store。而PageStore创建的时候传入的是中间件的enhancer。

// redux/apply_middleware.dart
StoreEnhancer<T> applyMiddleware<T>(List<Middleware<T>> middleware) {  return middleware == null || middleware.isEmpty      ? null      : (StoreCreator<T> creator) => (T initState, Reducer<T> reducer) {            assert(middleware != null && middleware.isNotEmpty);
           final Store<T> store = creator(initState, reducer);            final Dispatch initialValue = store.dispatch;            store.dispatch = (Action action) {              throw Exception(                  'Dispatching while constructing your middleware is not allowed. '                  'Other middleware would not be applied to this dispatch.');            };            store.dispatch = middleware                .map((Middleware<T> middleware) => middleware(                      dispatch: (Action action) => store.dispatch(action),                      getState: store.getState,                    ))                .fold(                  initialValue,                  (Dispatch previousValue,                          Dispatch Function(Dispatch) element) =>                      element(previousValue),                );
           return store;          };}

这里的逻辑其实就是将所有的middleware的处理函数都串到store的dispatch,这样当store进行dispatch的时候所有的中间件的处理函数也会被调用。下面为各个处理函数的执行顺序,

首先还是component中的dispatch D1 会被执行,然后传递给store的dispatch,而此时store的dispatch已经经过中间件的增强,所以会执行中间件的处理函数,最终store的原始dispatch函数D2会被执行。

总结

通过上面的内容,现在我们可以知道一个action是如何一步步的派送给effect,reducer去进行处理的,我们也可以通过middleware的方式去跟踪state的变化,这样的扩展性给框架本身带来无限可能。

Image placeholder
JasonG
未设置
  28人点赞

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

推荐文章
Linux中的Kdump服务

我们知道在Linux中系统分为内核态和用户态,一般用户行为都发生在用户态,内核自我管理。但如果内核出现错误崩溃了,可以使用Kdump来分析错误原因。Kdump服务提供了内核的崩溃转储机制,可以在内核崩

揭秘|每秒千万级的实时数据处理是怎么实现的?

01背景闲鱼目前实际生产部署环境越来越复杂,横向依赖各种服务盘宗错节,纵向依赖的运行环境也越来越复杂。当服务出现问题的时候,能否及时在海量的数据中定位到问题根因,成为考验闲鱼服务能力的一个严峻挑战。线

Linux中的软链接和硬链接

微信公众号:爱问CTO专业编程问答社区www.askcto.com 问题出现问题1:Linux中软链接和硬链接的区别?问题2:Linux系统内部是通过文件名访问文件的吗?基础铺垫平时在Linux系统中

css width是什么意思

width是宽度的意思,它是CSS中的一个用于设置元素宽度的属性。width属性设置元素的宽度。width属性定义元素内容区的宽度,在内容区外面可以增加内边距、边框和外边距;行内非替换元素会忽略这个属

pymysql fetchone () , fetchall () , fetchmany ()

最近在用python操作mysql数据库时,碰到了下面这两个函数,标记一下: 1.定义 1.1fetchone(): 返回单个的元组,也就是一条记录(row),如果没有结果则返回None 1.2fet

笨办法学 Linux Bash:Shell、`.profile`、`.bashrc`、`.bash_history`

Bash:Shell、.profile、.bashrc、.bash_history。 当使用CLI(命令行界面)来使用Linux时,你正在与一个名为shell的程序进行交互。所有你输入的都传递给she

vue中的diff算法详解

1.当数据发生变化时,vue是怎么更新节点的?要知道渲染真实DOM的开销是很大的,比如有时候我们修改了某个数据,如果直接渲染到真实dom上会引起整个dom树的重绘和重排,有没有可能我们只更新我们修改的

聊聊chronos的DeleteBgWorker

序本文主要研究一下chronos的DeleteBgWorkerDeleteBgWorkerDDMQ/carrera-chronos/src/main/java/com/xiaojukeji/chron

当年“你说什么,我都能实现”的软件公司,后来都是怎么死的?

在 #“我,80后,曾经靠副业的收入买车买房”# 的评论区里,有读者问,十几年前,圈内有不少软件公司,规模大小不一,遍布各个行业,但这几年似乎都没动静了,他们还活着吗?我说,撇开纯做“劳工”输出的外包

YouTube 的视频推荐是如何实现的?

最近,谷歌研究人员发表了一篇论文,并在RecSys2019(丹麦哥本哈根)的论坛上公布,论文中对他们的视频平台Youtube用户视频推荐方式进行了阐述。在这篇文章中,笔者将试着总结我阅读这篇论文后的发

打造高逼格、可视化的Docker容器监控系统平台

关于Docker技术的文章之前也断断续续写了几篇:Docker容器系列文章|Docker技术入门(一)Docker容器系列文章|Docker技术入门(二)Docker容器系列文章|这20个Docker

基于中台实践的DevOps平台有何不同?

为了响应快速变化的市场需求,业务要快速迭代。IT正在向云原生架构转型,解放架构自由度,最大化业务敏捷性,解耦合、敏捷开发、快速部署是当下企业的追求,可以消除研发与运维之间鸿沟的DevOps(研发运维)

JavaScript的DOM应用笔记

获取a标签:document.links获取img标签:document.images获取form标签:document.forms

JavaScript的DOM应用笔记

通过id获取:document.getElementById()写入HTML:innerHTML()内部outerHTML()外部

JavaScript的DOM应用笔记

操作元素属性:element.attribute=newvalueelement.setAttribute(attribute,value)element.getAttribute(attribute

JavaScript的DOM应用笔记

通过id或标签名获取元素对象:getElementById(‘id’)getElementsByTagName(‘标签名’)getElementsByName(‘name’)getElementByC

JavaScript的DOM应用笔记

这里的修改className属性,因为长度会动态改变,所以需要将修改的下标设为0,

初识 redux

redux介绍 单向数据流:从父组件流向子组件,兄弟组件无法共享数据 State:React中的状态,是只读对象,不可直接修改 Reducer:基本函数,用于对State业务的处理 Action:普通

初识 redux

redux介绍 单向数据流:从父组件流向子组件,兄弟组件无法共享数据 State:React中的状态,是只读对象,不可直接修改 Reducer:基本函数,用于对State业务的处理 Action:普通

UI2CODE再进化!结合Redux的框架升级!

背景UI2CODE的目标是通过分析视觉稿得到对应的代码,让AI提高开发效率。然而过去静态化页面的产出,不能得到业务场景的需求。针对于此,我们以UI2CODE自动化开发为基底,结合Redux的消息机制,

Java 程序员眼中的 Linux_1.0.Linux 介绍

Linux介绍 Linux这个名字 Linux的Wiki介绍:http://zh.wikipedia.org/zh/Linux Linux也称:GNU/Linux,而其中GNU的全称又是:Gnu’sN

[Java 程序员眼中的 Linux] Linux 下常用压缩文件的解压、压缩

Linux下常用压缩文件的解压、压缩 常用压缩包解压命令整理 Linux后缀为.tar.gz格式的文件-解压 命令:tarzxvfXXXXXX.tar.gz Linux后缀为.bz2格式的文件-解压

css3怎么实现菱形渐变?

css3怎么实现菱形渐变1、实现菱形渐变,首先需要实现一个菱形,使用了clip-path属性:.diamond{ width:200px; height:200px; clip-path:pol

jquery怎么实现淡入淡出效果?

jquery怎么实现淡入淡出效果?jquery实现淡入效果使用fadeIn()、淡出效果使用fadeOut(),来回切换使用fadeToggle()。●jQueryfadeIn()方法:用于淡入已隐藏

css定位怎么实现居中?

css定位怎么实现居中?使用绝对定位absolute是一种常用、兼容性很好的方式。.element{ width:600px;height:400px; position:absolute; left