dva & redux

1-1,背景发展描述

(1)Web 1.0:静态网页,纯内容展示

从web诞生到2005年, 那时的网站采用后端 MVC 模式,前端只是后端 MVC 的 V,这种局面我们称为前后端不分离的时代;

Model(模型层):提供/保存数据
Controller(控制层):数据处理,实现业务逻辑
View(视图层):展示数据,提供用户界面

(2)Web 2.0:动态网页,富交互,前端数据处理

从2005年后ajax被关注,通过大量使用ajax技术,不需要刷新页面,就可以使前端与服务器进行网络通信。前端也能独立的得到数据,然后处理数据,并展示出来,这种局面我们称为前后端分离的时代;

ajax技术促成了 Web 2.0 的诞生,在当时ajax是一项革命性的技术,颠覆了用户体验。

(3)需求使然,前端框架相应产生

为了解决浏览器兼容问题,jQuery,Dojo,ExtJS,Mootools等前端框架相继诞生,我们可以用这些框架频繁发送 AJAX 请求到后台,在得到数据后,再用这些 框架更新 DOM 树。前端能读写数据,切换视图, 用户交互, 既然要做这么多事,必然的网页的代码体积越来越大。

(4)DOM操作问题引发的进步

对于jQuery来说,DOM操作需要反复地操作文档,并触发网页的重绘和重排,这会严重影响网页的性能,当项目中大量的代码去操作DOM时,最严重的可能导致页面卡死,所以jQuery无法应对越来越复杂的前端开发工作。

(5)MVVM流行

为了解决上述问题,获得更优秀的用户体验,组件化开发和MVVM的开始兴起。随着越来越多的框架出现,网页逐渐由web site演变成web app,使得复杂的单页面应用的出现。

在这里插入图片描述

1-2,react特性

React是用于构建用户界面的JavaScript库, 起源于Facebook的内部项目,因为对市场上MVC框架都不满意,于是开发了React用来架设Instagram的网站。2013年5月29日上线。

特点:
它采用声明范式来描述应用,建立虚拟dom,支持JSX语法,通过react构建组件,能够很好的去复用代码;

缺点:
react抽离了dom,使我们构建页面变得简单,但是对于一个大型复杂应用来说,只有dom层的便捷是不够的,如何组织、管理应用的代码,如何在组件间进行有效通信,这些它都没有解决。所以在我们需要的时候可以借助状态管理架构redux & dva。

2-1,redux

Redux:状态管理器。可以分模块或集中对数据状态进行管理。

它并不是只针对react结合,它可以在各种框架中结合使用,但是在react项目中使用较广泛。

2-2,redux核心概念
store: 仓库,用来存储状态state
state: 状态
reducer: 是一个函数,该函数接收两个参数state和action,负责对状态进行同步管理
action: 是一个对象,且必须有一个属性type,该对象通过store.dispatch来触发,作为reducer接收的参数来实现对state的操作
// 创建仓库
import {
  createStore,//创建仓库函数
} from 'redux';
const store = createStore(reducer);
// store中存放状态,
const state = store.getState();
// 操作状态
import {
  createStore
} from 'redux';
const initState = {
  name:'橘子',
  price: '4',
}
function reducer(state=initState,action){
  console.log('执行reducer',action)
  if(action.type === 'EDIT'){
     state.price  = action.payload.price;
  }
  return state;
}
const store = createStore(reducer);
const state = store.getState();
store.dispatch({
  type:'EDIT',
  payload: {
     price: '9'
  }
})
// 可以发现当执行dispatch时会触发reducer
console.log('dispatch调用后',state)
// 工厂模式批量返回action
function change(num){
  return {
    type: 'EDIT',
    payload: {
      price:num,
    }
  }
}
store.dispatch(change('10'))
console.log('调用后',state)
2-3,在项目中使用redux

在store仓库下,我们通常会建4个文件夹,分别是

  • state:最初的状态,
  • reducer:官方建议不要在reducer中写逻辑,逻辑集中在action中
  • actionType:在执行时使用,存放type,可以对请求的type进行集中管理
  • actionCreator:action制造者,存放所有的action(同步/异步action)

使用详见项目。

3-1,什么是dva

dva 是基于现有应用架构 (redux + react-router + redux-saga + fetch)的一层轻量封装,没有引入任何新概念。

dva是基于react+redux最佳实践上实现的封装方案,简化了redux和redux-saga使用上的诸多繁琐操作。

目录结构:

├── /mock/           # 数据mock的接口文件
├── /src/            # 项目源码目录
│ ├── /components/   # 项目组件
│ ├── /routes/       # 路由组件(页面维度)
│ ├── /models/       # 数据模型
│ ├── /services/     # 数据接口
│ ├── /utils/        # 工具函数
│ ├── route.js       # 路由配置
│ ├── index.js       # 入口文件
│ ├── index.less    
│ └── index.html    
├── package.json     # 定义依赖的pkg文件
└── proxy.config.js  # 数据mock配置文件

在 dva 中,通常需要 connect Model的组件都是 Route Components,组织在/routes/目录下;而/components/目录下则是纯组件基本不需要用到state。

3-2,dva的特性
  • 易学易用:仅有 6 个 api,对 redux 用户尤其友好
  • elm 概念:通过 reducers, effects 和 subscriptions 组织 model
  • 插件机制dva-loading 可以自动处理 loading 状态,不用一遍遍地写 showLoading 和 hideLoading

elm概念

elm是一门专注web前端的纯函数式编程语言。(redux的核心reducer就是受到了elm的启发)

插件 dva-loading

用于监听页面的请求是否完成(主要是ajax请求是否完成),是通过直接监听异步的effect,在异步请求发出的那一刻开始持续监听该请求方法的状态,在异步请求结束时,loading的状态改为false并停止监听。

const isLoading = loading.effects['user/query'],其中 user/query 是 model 中的异步请求方法。

在dva中依赖于redux-saga,将每个路由和redux-saga库相连,进入页面则路由对应的组件会自动关联redux-saga,触发相应的reducer方法,从而更新loading中的相应bool。

3-3,dva的基础saga

redux-saga作为react的一个中间件对数据进行处理,主要集中处理react框架中的异步action,采用监听的形式进行工作。在action被传递到reducer之前进行了一次拦截,然后启动异步任务,等异步任务执行完成后再发送一个新的action,调用reducer去修改状态数据。

蓝色的圈圈内可以看做是一堆saga,他们需要和外部进行I/O交互,等交互完成后再修改store中的状态数据。redux-saga的主要功能就是管理这一堆saga。
在这里插入图片描述

redux-saga的api : (redux-saga的api有很多)

  • call:函数调用
  • select:获取Store中的数据
  • put:向Store发送action
  • takeEveny:在Store上等待指定action

yield call & yield select

*collectionProject({ payload }, { call, put, select }) {
      const res = yield call(collectionProject, payload);
      const myState = yield select(state => state.projectManage);
      if (res && res.code === 0) {
        yield put({
          type: 'projectList',
          // 分页的查询参数
          payload: {
            current: 1,
            name: '',
            size: myState.projectPageSize,
          },
        });
      }
    },

yield takeEvery

*rootSaga() {
    yield takeEvery(actionTypes.collectionProject, goAge)  
    // 有对应的type是collectionProject的action发起时,执行goAge() 
}
*goAge(){
    console.log('123');
}

yield put直接调用reducer是同步的,但是调用非reducer的方法是异步的。

function onHandlePress () {
 	dispatch({
      type: 'formAndstepForm/getData',
      payload: 'demo',
    });
}

// 所有后续的操作都通过 saga 来管理。
 effects: {
  * getData({payload}, { put, select, call }) {
    console.log('start');
    yield put(
      {
        type: 'upState',
        payload,
      },
    );
    console.log('end')
  },
},
reducers: {
  upState: (state, { data: data }) => {
    for (let i = 0; i < 8; i++) {
      console.log(i);
    }
    return { ...state, data: data };
  },
};
function onHandlePress () {
 	dispatch({
      type: 'formAndstepForm/getData',
      payload: 'demo'
    });
}

 effects: {
  * getData({payload}, { put, select, call }) {
    console.log('start');
    yield put(
      {
        type: 'testAsync',
        payload,
      },
    );
    console.log('end')
  },
  *testAsync({},{put}){
    yield put(
      {
        type: 'upState',
        data: 'this is 2 demo',
      },
    );
  }
},
reducers: {
  upState: (state, { data: data }) => {
    for (let i = 0; i < 8; i++) {
      console.log(i);
    }
    return { ...state, data: data };
  },
};

// j结果 start 0-8 end

// 结果start end 0-8

所有业务代码都存于 saga 中,不再散落在各处,可以证明对异步处理友好;

3-4,dva的api
import dva from 'dva';
import { Component } from 'react';
import createLoading from 'dva-loading';
import history from '@tmp/history';

let app = null;

export function _onCreate() {
  const plugins = require('umi/_runtimePlugin');
  const runtimeDva = plugins.mergeConfig('dva');
  // 1,创建一个 dva 实例
  app = dva({
    history,
    ...(runtimeDva.config || {}),
    ...(window.g_useSSR ? { initialState: window.g_initialData } : {}),
  });

  // 2,装载插件dva-loading,可以用于配置hook以及其他插件
  app.use(createLoading());
  (runtimeDva.plugins || []).forEach(plugin => {
    app.use(plugin);
  });
// 3,注册Model
  app.model({ namespace: 'global', ...(require('E:/project/src/models/global.js').default) });
  app.model({ namespace: 'list', ...(require('E:/project/src/models/list.js').default) });
  app.model({ namespace: 'login', ...(require('E:/project/src/models/login.js').default) });
  app.model({ namespace: 'menu', ...(require('E:/project/src/models/menu.js').default) });
  app.model({ namespace: 'project', ...(require('E:/project/src/models/project.js').default) });
  app.model({ namespace: 'setting', ...(require('E:/project/src/models/setting.js').default) });
  app.model({ namespace: 'user', ...(require('E:/project/src/models/user.js').default) });
  return app;
}

export function getApp() {
  return app;
}

export class _DvaContainer extends Component {
  render() {
    const app = getApp();
    // 4,配置路由
    app.router(() => this.props.children);
    // 5,启动应用
    return app.start()();
  }
}

在这里插入图片描述
还有一个取消Model注册,app.unmodel(namespace),可清理对应命名空间的model层。

 app._models.forEach((model:any) => {
   app.unmodel(model.namespace);
 });
app._models = [];
3-5,dva与传统redux的区别

对于dva,通过model层,只需要一个文件就可以实现对数据的集中管理, 数据流程如下图:
在这里插入图片描述

export default {
 
  namespace: 'example',   //表示对于整个应用不同的命名空间,以便通过this.props.example访问,和当前model文件名相同就好之前的reducer名字相同,是全局state的属性,只能为字符串
 
  state: {initText:"hello"},     //表示当前的example中的state状态,这里可以给初始值
 
  subscriptions: {
    setup({ dispatch,history }) {  // 订阅,可以监听服务器连接,键盘输入,路由,状态等的变化
      return history.listen(({ pathname, search }) => {
        if (pathname === '/user/login') {
          dispatch({
            type: 'login/getEncrypt',
          });
        }
      });
    },  
  },
 
  effects: {
    *fetch({ payload }, { call, put }) { 
     //payload是从组件router传递过来的参数,
     //这里的call方法可以使用payload参数传递给后台程序进行处理这里可以调用service层的方法进行调用后端程序,
     //这里的put表示存储在当前命名空间example中,通过save方法存在当前state中
      yield put({ type: 'save' });
    },
  },
    
  //用来保存更新state值 上面的put方法调用这里的方法
  reducers: {
    save(state, action) {
      return { ...state, ...action.payload };
    },
  },
};

上面的model是需要注册到src/index.js中的app.router(require(‘./router’).default)否则后面和route组件无法用connect传递数据,如果定义了多个model,那么需要使用多个app.model()来传递model数据;


  • 18
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值