Redux

在学习Redux之前,不妨先了解下Flux:

Flux

  • 同MVC一样,是一种架构思想,但更简单清晰
  • 解决软件的结构问题,提供了一套数据流动方案
  • 通过事件和监听实现数据单向流动,中心化控制

  • View: 视图层
  • Action(动作):视图层发出的消息
  • Dispatcher(派发器):用来接收Actions、执行回调函数
  • Store(数据层):用来存放应用的状态,动态更新Views页面

一个基本的流程可以描述为

  • Your Views "Dispatch" "Actions"(视图触发事件)
  • Your "Store" Responds to Dispatched Actions(store触发回调)
  • Your Store Emits a "Change" Event(store触发change事件)
  • Your View Responds to the "Change" Event(视图接收到事件重新渲染)

与React关系可以理解为:

Flux:是一个系统架构,用于推进应用中的数据单向流动
React:是一个JavaScript库,用于构建“可预期”和“声明式”的可组合式Web用户界面

问题

  • 代码冗余
  • dispatcher实例手动创建
  • store既保存状态数据,又有处理逻辑:直接处理数据
  • 程序可能会涉及多种数据结构,必然导致有多个store
  • 深层次组件通信问题

参考

Flux架构入门教程 - ryfeng

Flux For Stupid People

一篇漫画,图解Flux

Redux

Redux是基于Flux架构思想的一个库的实现,JavaScript状态容器,提供可预测化的状态管理。

设计思想

  • Web 应用是一个状态机,视图与状态是一一对应的。 
  • 所有的状态,保存在一个对象里面。
  • 为应用程序提供一个可预测的状态容器

三大原则

  • 单一数据源
  • State 是只读
  • 使用纯函数来执行修改

显著特点

  • 可预测性(Reducer 是纯函数)
  • 可扩展性(middleware)

核心元素

  • store
  • action
  • dispatch
  • reducer
  • subscribe

Redux中的 reducer 就是一个纯函数,store.dispatch(_action) 会自动触发 reducer 方法,更新state。

注意,reducer方法不会改变state,而是返回一个全新的state对象。

关于纯函数:同样的输入,必定得到同样的输出

  • 不得改写参数
  • 不能调用系统 I/O 的API
  • 不能调用Date.now()Math.random()等不纯的方法,因为每次会得到不一样的结果

适用场景

多交互、多数据源

  • 用户的使用方式复杂,不同身份的用户有不同的使用方式(比如普通用户和管理员),多个用户之间可以协作
  • 与服务器大量交互,或者使用了WebSocket
  • View要从多个来源获取数据

关于Redux的生态系统,请参见:http://www.redux.org.cn/docs/introduction/Ecosystem.html

store

本质:状态树

let { subscribe, dispatch, getState } = createStore(reducer);

关于createStore的基本实现,辅助理解

function createStore(reducer, initialState) {
    //闭包私有变量 
    var currentReducer = reducer;
    var currentState = initialState;
    var listeners = [];

    function getState() {
      return currentState;
    }

    function subscribe(listener) {
      listeners.push(listener);

      return function unsubscribe() {
        var index = listeners.indexOf(listener);
        listeners.splice(index, 1);
      };
    }

    function dispatch(action) {
        currentState = currentReducer(currentState, action);
        listeners.slice().forEach(listener => listener());
        return action;
    }

    //返回一个包含可访问闭包变量的公有方法
    return {dispatch, subscribe, getState};
}

store可以看作是对reducer的封装,通过store.dispatch(action)自动触发对reducer的调用,而不是直接调用reducer(currentState, action),在一定程度上可以避免频繁传参,以更好地对store进行统一管理。

Action & Action Creator & bindActionCreators

  • action:传递操作消息
  • action creator:操作制造机
  • bindActionCreators:对dispatch封装
var actionCreatorsNew = bindActionCreators(actionCreators, store.dispatch);

借鉴store对reducer的封装,对store.dispatch作封装,自动把actionCreators绑定到dispatch,使actionCreators成为具有操作全局state的函数集合。

其中,actionCreators表示action集合。触发action,会自动调用dispatch(action),避免直接对dispatch的调用。 

middleware  

异步场景:在异步操作结束后自动执行reducer 

即,如何在操作结束时,自动送出第二个 Action 

扩展点:dispatch()发出时,reducer()处理前

对于中间件,举例,日志中间件 redux-logger

import { applyMiddleware, createStore } from 'redux';
import createLogger from 'redux-logger';

const logger = createLogger();
const store = createStore(reducer, initial_state,
  applyMiddleware(logger)
);

other,请注意中间件的引入次序。

给出 applymiddleware 的实现,仅供参考

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    var store = createStore(reducer, preloadedState, enhancer);
    var dispatch = store.dispatch;
    var chain = [];

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    };
    chain = middlewares.map(middleware => middleware(middlewareAPI));
    dispatch = compose(...chain)(store.dispatch);

    return {...store, dispatch}
  }
}

其中,compose 用于组合函数、串联链式执行,顺序自右向左,维护扩展方便 

var greeting = (firstName, lastName) => 'hello, ' + firstName + ' ' + lastName
var toUpper = str => str.toUpperCase()
var fn = compose(toUpper, greeting)
console.log(fn('jack', 'smith'))  // ‘HELLO,JACK SMITH’

异步情况,涉及发出三种不同种类的 Action  

  • 操作发起时的 Action
  • 操作成功时的 Action
  • 操作失败时的 Action

同时,state需要维护、以反映不同的操作状态 

let state = {
  // ... 
  isFetching: true,  // 表示是否在抓取数据
  didInvalidate: true,  // 表示数据是否过时
  lastUpdated: 'xxxxxxx'  // 表示上一次更新时间
};

整个异步操作流程应该是这样的  

  • 操作开始时,送出一个 Action,触发 State 更新为"正在操作"状态,View 重新渲染
  • 操作结束后,再送出一个 Action,触发 State 更新为"操作结束"状态,View 再一次重新渲染

在Redux中,中间件是纯粹的函数,有明确的使用方法并且严格的遵循以下格式:

var anyMiddleware = function ({ dispatch, getState }) {
   return function(next) {
       return function (action) {
          // 你的中间件业务相关代码
       }
   }
}

所有的这些,applyMiddleware会全部替我们封装实现。 

异步解决方案可以引入中间件 redux-thunk 或 redux-promise  

  • redux-thunk:解决stroe.dispatch()的参数只能为action对象、不能是函数的问题
    • 返回函数的 Action Creator(返回的function在合适的时机dispatch action)
  • redux-promise:允许 Promise 对象作为stroe.dispatch()的参数,以类似同步的方式来组织代码
    • 返回 Promise 对象的 Action Creator 
    • 或者,Action 对象的 payload 属性为 Promise 对象

但是,两者均是相对原始的解决方案,在action需要组合、取消时操作不易处理。最佳实践dva推荐 redux-saga,可测试、可mock、声明式的指令,管理actions,处理异步逻辑,管理所有的业务逻辑。

React-Redux

组件分类

  • UI组件:presentational component,无状态的纯组件,只负责UI视觉
  • 容器组件:container component,有状态,负责管理数据和逻辑

Redux的重要思想就是:容器组件和展示组件的分离

业务逻辑:针对UI组件

  • 输入逻辑:外部的数据(即state对象)如何转换为 UI 组件的参数
  • 输出逻辑:用户发出的动作如何变为 Action 对象,从 UI 组件传出去

在网上看到一个非常不错的关系图,分享给大家

connect

将 UI 组件生成容器组件:

const HocView = connect(mapStateToProps, mapDispatchToProps)(myComp)

其中,mapStateToProps负责输入逻辑、将状态数据state映射到 UI 组件的参数props,mapDispatchToProps负责输出逻辑、将用户对 UI 组件的操作映射成 Action。

mapStateToProps会订阅Store,每当state更新,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染。

<Provider>

用来实现对store的全局访问,使容器组件获取到state:

render(
  <Provider store={store}>
    <Root />
  </Provider>,
  document.getElementById('root')
)

原理是利用React的 context 属性:  

class Provider extends Component {
  getChildContext() {
    return {
      store: this.props.store
    };
  }
  render() {
    return this.props.children;
  }
}

Provider.childContextTypes = {
  store: React.PropTypes.object
}  

子组件通过 this.context.store 获取。一个简单的计数器例子,供参见。

Redux扩展: redux-devtools-extension

  • 时间旅行式调试工具,time-travelling tool
  • 热重加载,hot reloading
  • 基于数据不可变性(immutable)

使用步骤

  • 在Chrome中安装Redux Devtools扩展
  • npm安装 redux-devtools-extension
  • import引入

参考

Redux 中文文档redux-tutorial

Redux教程(1-4) - 阮一峰Redux视频前30集后30集; 

redux在react中的应用(基础篇)redux入门教程

看漫画,学Reduxredux 三重境 - 对 redux 最佳实践的思考和总结

React 数据流管理架构之 Redux 介绍

redux-saga

前面提到 redux-saga 可以作为 Redux 的异步解决方案,简单学习之,为后面学习 dva 作个铺垫。

A Redux middleware for handling side effects (异步任务).

Redux中间件,基于ES6的Generator功能,用于管理应用程序Side Effect的库,副作用例如

  • 异步获取数据
  • 访问浏览器缓存

建议先了解下 Generator语法。通过redux-saga中间件将 Saga 与 Redux Store 建立连接:

import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import reducer from './reducers'
import mySaga from './sagas'

const sagaMiddleware = createSagaMiddleware();
const store = createStore(reducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(mySaga); /// then run the saga

其中,'./sagas' 用于处理所有异步操作逻辑,'./reducers' 用于处理action对stage更新。

 

参考

redux-saga | 中文教程redux-saga-beginner-tutorial

dva

由支付宝前端团队开发,相关历史可参见:支付宝前端应用架构的发展和选择: 从 roof 到 redux 再到 dva

  • 基于现有应用架构 (redux + react-router + redux-saga 等)的一层轻量封装
  • React + Redux 最佳实践,简化使用 redux 和 redux-saga 时繁杂的操作
  • 组件耦合度低
  • 结合了 react 和 vue 两者的优点,但格式固定、降低了灵活度
  • 除 react 和 react-dom 外,封装了所有其他依赖

相关简介参见:dva - what&why -简介

注意,dva 是 framework,而 redux 是 library。

最核心功能是提供 app.model 方法,用于把 reducer, initialState, action, saga 封装到一起

每个路由对应一个model,这个model掌管该路由的所有状态(action、state、reducer、sagas),组件想改变状态只要dispatch type即可。 

参考

React + Redux 最佳实践 - dva 基于此封装

posted @ 2018-07-09 20:13  万箭穿心,习惯就好。  阅读(377)  评论(0编辑  收藏  举报