关于dva框架的二三事

前言

我开通了一个微信公共号“王和阳的航海日志”,在上面记录着自己的学习、思考、实践和成长的过程,欢迎关注、交流和拍砖。

公共号二维码

最近在项目里用了DVA,一个基于Redux的前端应用开发框架,用dva能让我们省去配置项目的一堆麻烦事儿。关于Dva的使用和介绍这里就不多说了,官方文档已经讲得很详细了。下面简单结合我自己的实践经历,讲讲DVA的一些思想以及一些关于数据流向的想法。

Dva的框架和由来

一图胜前言,首先我们看下传统的React项目的组件结构是怎么样的:

传统react项目结构图

如果<TodoList/><AddTodoBtn/>想要发生联系,则只能通过父组件<App/>来实现,这个方式在页面比较简单的时候还能够胜任,但若项目复杂起来之后,整个页面的数据流向就会变得如下图所示:

React超长的数据流

在这种情况下对项目进行维护绝对是个灾难,同时对这样的项目进行改动也是很困难的事儿,因为不同组件之间耦合的地方太多了,所以我们需要对state做额外的管理,于是我们就使用了Redux,那么项目结构就变成了这样:

使用了Redux后的项目

可以看到这已经把state和处理逻辑从 里面抽取出来了, 原先的addfinish操作也变成了reducer里的函数。因为<TodoList/><AddTodoBtn/>都是 Pure Component, 那么通过 connect 方法使这些组件建立起与 store 的联系,同时通过 dispatch 向 store 发送 action, 促发 store 的状态进行变化, 一旦状态有变, 因为组件和store是被 connect,那么组件也就会随之更新,而且这个过程是可以被拦截的,所以我们就可以很方便地增加各种 Middleware,从而实现各种自定义功能, 例如log、高阶组件,并且这样的结构耦合度更低, 复用度更高, 扩展性更好。

需要注意的是,上面的项目结构其实是不包含异步处理的,则为了处理异步请求,我们引入了redux-sageredux-saga会拦截异步action并发起http请求,以发送type=add的异步action网络请求来说,若请求成功,则继续发送一个type=addSuccess的action,若请求失败,则发送一个type=addFail的action

在了解了上面这些概念后,使用了dva后项目的结构也就呼之欲出了:
用了dva后的结构
dva本身就是React + Redux + saga的组合,而图中红框的部分就是dva,可以很清楚地看到,dvastoresaga结合成了一个model,同时增加了一个subscription来订阅其他来源的action(例如键盘操作、路由变化)

下面简单总结下dva中的一些概念以及自己的一些思考

state

state 表示 Model 的状态数据,通常表现为一个 javascript 对象(当然它可以是任何值),且需要把这个state当做不可变数据(immutable data)来处理,保证每次得到的state都是全新对象,新旧对象间不存在引用关系,在检测两个state里的复杂对象时只需要比较其属性的引用即可,这样做的好处是能够提升性能,便于测试和追踪变化(可用来实现神奇又好玩的时光机穿梭神器 redux-devtool)。具体到代码里就如下图所示:

 state: {
    keyword: '',
    moduleList: [],
  },
.....
return { ...state, keyword };

action 和 dispatch

action 是改变 state 的唯一途径,是一个普通的 javascript 对象,它描述了一个行为且是改变 state 的唯一途径。从用户UI操作事件、网络请求回调和 WebSocket 等其他地方获得的数据,最终都会通过 dispatch 函数调用一个 action,从而改变对应的数据。action 必须带有 type 指明具体的行为名称,且能附带上额外的信息。

dispatching 是一个用于触发 action 的函数,且 dispatch 是在组件 connect Models以后,才能通过 props 传入。actiondispath存在的意义就在于将“如何改变数据”和“改变数据”这两件事分开进行,让我们对如何改变state有更清晰的思路。

dispatch({
  type: 'add',
  payload:{
    item:'a'
  }
});

reducer

reducer 是描述如何改变数据的,它接受两个参数(之前已经累积运算的结果和当前要被累积的值),并返回一个新的累积结果。

通过 actions 中传入的值,与当前 reducers 中的值进行运算获得新的值(也就是新的 state)。需要注意的是 Reducer 必须是纯函数,即同样的输入必然得到同样的输出,它们不应该产生任何副作用,或者用更加专业的话说,这个函数是幂等的。

effect

effect 被称为副作用,在我们的应用中,最常见的就是异步操作(例如网络请求)。它来自于函数式编程的概念,之所以叫副作用是因为同样的输入不一定获得同样的输出。现在函数式编程大行其道的原因主要有以下两点:

  • 函数式编程是面向数学的抽象,更加符合人的思维逻辑
  • 函数是引用透明且没有副作用,不依赖外部的状态也不改变外部的状态。
  • 因为函数是不可变的,则由于多个线程之间不共享状态,不会造成资源争用(Race condition),也就不需要用来保护可变状态,也就不会出现死锁,这样可以更好地进行并发,尤其是在对称多处理器(SMP)架构下能够更好地利用多个核提供的并行处理能力。要知道现在计算机的计算能力的增加已经不是依靠主频频率的提升,而是更依赖于CPU核心个数的增加,这一点带来的好处是母庸置疑的5

(如果你想了解更多关于函数式编程的信息,可以阅读JS函数式编程指南。)

dva 为了控制副作用的操作,引入了redux-sagas做异步流程控制,且因为采用了generator的相关概念,所以能将异步转成同步写法,从而将effects转为纯函数。

  effects: {
    * createModule({ payload: { formData} }, { call, put }) {
      const result = yield call(Services.createModule, formData);
      if (result) {
        yield put({ type: 'startSearch'});
      }
    }
  }

subscription

subscriptions 是一种从 获取数据的方法,它来自于 elm。

Subscription 用于订阅一个数据源,然后根据条件 dispatch需要的action。数据源可以是服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等。


app.model({
  namespace: 'count',
  subscriptions: {
    setup({ dispatch, history }) {
      return history.listen(({ pathname }) => {
        // 第1次进入页面,请求模块数据
        if (pathname === '/module') {
          dispatch({ type: 'startSearch'});
        }
      });
    },
  }
});

简短的完整代码

import dva, { connect } from 'dva';
import { Router, Route } from 'dva/router';
import React from 'react';
import styles from './index.less';
import key from 'keymaster';

const app = dva();

app.model({
  namespace: 'count',
  state: {
    record: 0,
    current: 0,
  },
  reducers: {
    add(state) {
      const newCurrent = state.current + 1;
      return { ...state,
        record: newCurrent > state.record ? newCurrent : state.record,
        current: newCurrent,
      };
    },
    minus(state) {
      return { ...state, current: state.current - 1};
    },
  },
  effects: {
    *add(action, { call, put }) {
      yield call(delay, 1000);
      yield put({ type: 'minus' });
    },
  },
  subscriptions: {
    keyboardWatcher({ dispatch }) {
      key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) });
    },
  },
});

const CountApp = ({count, dispatch}) => {
  return (
    <div className={styles.normal}>
      <div className={styles.record}>Highest Record: {count.record}</div>
      <div className={styles.current}>{count.current}</div>
      <div className={styles.button}>
        <button onClick={() => { dispatch({type: 'count/add'}); }}>+</button>
      </div>
    </div>
  );
};

function mapStateToProps(state) {
  return { count: state.count };
}
const HomePage = connect(mapStateToProps)(CountApp);

app.router(({history}) =>
  <Router history={history}>
    <Route path="/" component={HomePage} />
  </Router>
);

app.start('#root');


// ---------
// Helpers

function delay(timeout){
  return new Promise(resolve => {
    setTimeout(resolve, timeout);
  });
}

引用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值