think icon indicating copy to clipboard operation
think copied to clipboard

Redux是什么?

Open bytemofan opened this issue 8 years ago • 0 comments

redux是一个构建大型react应用的前端架构,结合了flux和函数式编程的思想。

flux

flux将一个应用分成四个部分:

  • View 视图层
  • Action 视图层发出的消息
  • Dispatcher 接收视图层发出的actions,执行回调函数
  • Store 存放应用的状态,发生变动时,更新View

各个部分关系大概如下:

image

函数式编程

举例:

function forEach(array,func){
    for(var i = 0; i < array.length; i++){
        func(array[i]);
    }
}
var a = ["a","b","c"];
forEach(a,function(obj){print(obj);});
forEach(a,function(obj){print(obj + 1);});

//输出结果
a
b
c
a1
b1
c1

Redux设计思想

  • web应用是一个状态机,视图和状态是一一对应的。
  • 所有的状态保存在一个对象里边。

Redux概念和API

  • action:应用数据的改变的描述
  • reducer:决定应用数据新状态的函数,接收应用之前的状态和一个action返回的新的数据状态
  • store:应用数据的存储中心
  • middleware: redux提供中间件的方式,完成一些flux流程的自定义控制,同时形成其插件体系

action

action的作用就是定义store将要更新数据的信息(对应的动作,对应的参数值),也就是UI组件发出的通知,告知store数据的state将要改变了,所以基本的结构可以像这样(其实可以理解为它就是定义store的一个更新数据方法的参数):

const ADD_TODO = "ADD_TODO";
store.dispatch({
     type:ADD_TODO,   //这个字段名必须要有
     text:"这个字段的名字随意,按照需要来"
});

因为一个应用action会非常之多,如果每次分发调用我们都要写一遍,肯定不值当,所以我们可以提供一个高阶函数来作为action的创建者:

function addTodo(text){
     return {
         type:"ADD_TODO",
         text:text
     }
}
store.dispatch(addTodo(text));

reducer

action描述了数据发生变化的事实,但却没有去更新数据。更新数据的操作就落到了reducer身上。

reducer是一个纯函数,它的作用是利用上一个状态的信息和action信息来产生下一个新状态。

(prevState,action) => newState

纯函数就是有什么样的输入有什么样的输出,纯函数是函数式编程的概念,遵循一些约束:

  • 不得改写参数
  • 不能异步调用(异步API,I/O,路由转换等)
  • 不能调用不纯的函数(Date.noew(),Math.random())

具体实现:

/**
* reducer 描述 
* @param  prevState      [之前状态]
* @param  action         [redux action]
* @return nextState      [新状态]
*/
function reducer(prevState, action) {
     var nextState = {...};
     switch(action.type) {
          case 'ADD_TODO': return Object.assign({}, prevState, nextState);
          case 'REMOVE_TODO': return Object.assign({}, prevState, nextState);
          ......
          default: return prevState;
     } 
}

应用刚创建时候的初始状态为undefined的,所以我们可以需要对它进行处理,也可以趁机赋默认值:

var initialState = {todos:...}
function reducer(prevState,action){
     if(typeof prevState === 'undefined'){
         return initialState; 
     }
     switch(action.type){...}
     return prevState;
}

当一个应用的reducer太多的时候,我们需要对reducer进行拆分,然后再进行组合:

function add(prevState, action) {}
function filter(prevState, action) {}
......

var reducers = redux.combineReducers({
     add:add,
     filter:filter
});

combineReducers方法是redux已经实现了的方法。

reducers不再负责整个应用状态的修改,而是将责任分配给其它的子reducer,每个子reducer相互独立,各有责任,也即分而治之的思想。

store

store是一个单例,存放了整个应用的所有状态数据,它有维护一颗状态树。

它连接了action,reducer和组件。

它提供了以下API:

* store.getState()    获取store里边的数据
* store.dispatch(action)  触发action来更新数据,是唯一的触发状态更新操作的方法。
* store.subscribe(listener) 设置监听函数,一旦某一个state改变,就自动执行这个函数,可以通过它返回的方法取消订阅
* store.replaceReducer(nextReducer) 允许你动态的去修改正在计算state的reducer

store是由redux模块的createStore方法创建的,可以通过一个简单实现来加深理解:

const createStore = (reducer)=>{
     let state;
     let listeners = [];
     const getState = ()=>state;
     
     const dispatch = (action)=>{
         state = reducer(state,action);
         listeners.forEach(listener => listener()); 
     };
     
     const subscribe = (listener)=>{
         listeners.push(listener);
         return ()=>{
              listeners = listeners.filter(l => l!==listener); 
         } 
     };

     const replaceReducer = (nextReducer)=>{
         reducer = nextReducer;
     }
     
     dispatch({});
     return {getState,dispatch,subscribe,replaceReducer}
}

middleware

middleware就是一个函数,它对store.dispatch进行了改造,在发出action和执行reducer这两步之间,添加一些扩展的功能。 简单的类似这个:

let next = store.dispatch();
store.dispatch = (action)=>{
     console.log('current action',action);
     next(action);
     console.log('next state',store.getState());
}

中间件工作流:

image

中间件用法:

import { applyMiddleware,createStore } from 'redux';
import createLogger from 'redux-logger';
const logger = createLogger();

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

applyMiddleware是redux提供传递中间件的方法。

有许多开源的中间件已经实现了很多事情了,如果没有,可能你需要自己实现一个。

Redux数据流动

image

Redux三大原则

单一数据源

整个应用的state被存储在一棵对象树种,并且这棵树值存在于唯一的一个store中。

  • 便于同构应用的开发。(后端服务序列化注入客户端数据)
  • 调试容易。(本地保存state)

State是只读的

唯一改变state的方法就是dispatch action,action是一个用于藐视已发生事件的普通对象。

  • 集中化处理操作,避免竞态出现。
  • action可以被日志打印,序列化,储存,后期调试或测试回放出来

使用纯函数来修改State

使用纯函数reducer来计算新的state。而且reducer可以被拆分成多个部分处理不同的业务逻辑,符合分而治之的思想。

Redux使用场景

你可能不需要redux:

  • 用户使用方式非常简单
  • 用户之间没有协作
  • 不需要与服务器的大量交互,也没有使用WebSocket
  • 视图层(View)只从单一来源获取数据

也就是说UI层非常简单,没有很多互动的时候都不许要Redux,使用了反而会增加应用的复杂度。

如果有以下场景则可以考虑使用redux(由此说明,redux并不是必须,还可以有其它的一些结构来代替)

  • 某个组件状态需要共享给其它组件(比如兄弟子组件)
  • 某个状态需要在任何地方都可以拿到(类似用户的登录状态在多个页面需要使用的情况等)
  • 一份组件需要改变全局状态
  • 一个组件需要改变另一个组件的状态,而且这两个组件之间并没有“血缘关系”

React-redux

redux和react之间没有关系。redux支持React,Vue,Angular,Ember,jQuery甚至原生JavaScript。

安装:

npm i react-redux --save

首先了解一个开发思想,容器组件和展示组件(纯组件)相分离的开发思想。

image

也就是UI组件复制UI的呈现,容器组件负责管理数据和逻辑。

react-redux提供connect()方法,用来连接UI组件和容器。

import {connect} from 'react-redux';
const VisibleTodoList = connect(
     mapStateToProps,
     mapDispatchToProps
)(TodoList);

mapStateToProps() 建立一个state对象(外部的,UI组件的state)到props对象的映射关系。

const mapStateToProps = (state)=>{
     return{
         todos: appTodos(state.todos,state.text)
     }
}

这个方法订阅store,当state更新时自动执行,重新计算UI组件的参数,触发UI的rerender。

connect可以省略这个参数,这时UI组件不订阅store,store更新不会引起UI组件的更新。

mapDispatchToProps() 建立UI组件的参数到store.dispatch的映射,它定义了哪些用户的操作应该当做action传给store,可以是函数也可以是对象。

作为一个函数,返回一个对象,该对象的每一个键值对都是一个映射,定义了UI组件的参数怎样发出Action。

const mapDispatchToProps = (dispatch,ownProps)=>{
     return {
          onClick:()=>{
              dispatch({
                   type:'ADD_TODO',
                   text:ownProps.text 
              }) 
          }
     }
}

作为一个对象,它的每一个键名应该对应UI组件的同名参数,键值应该是一个函数,会被当做Action creator,返回的Action则由redux自动发出。

const mapDispatchToProps = {
     onClick:(filter) =>{
         type:'ADD_TODO',
         text:text 
     }
}

这个方法订阅store,当state更新时自动执行,重新计算UI组件的参数,触发UI的rerender。

connect可以省略这个参数,这时UI组件不订阅store,store更新不会引起UI组件的更新。

<Provider>组件

connect方法生成容器组件之后,需要让容器组件获得state对象,才能生成UI组件的参数。

react-redux提供Provider组件,可以让容器拿到state。

import { Provider } from 'react-redux';
import { createStore } from 'redux';

import todoApp from './reducers/todoApp';
import App from './components/App';

let store = createStore(todoApp);

render(
     <Provider store={store}>
          <App/>
     </Provider>,
     document.getElementById('app')
);

另外,Provider组件可以和路由Router搭配使用时是没有任何问题的。

bytemofan avatar Jan 12 '17 02:01 bytemofan