本文的目的很简单,介绍Redux相关概念用法 及其在React项目中的基本使用

 

假设你会一些ES6、会一些React、有看过Redux相关的文章,这篇入门小文应该能帮助你理一下相关的知识

一般来说,推荐使用 ES6+React+Webpack 的开发模式,但Webpack需要配置一些东西,你可以先略过,本文不需要Webpack基础

入门,只是一些基础概念和用法的整理,更完整的内容推荐去看看文档,英文中文

(不过我个人认为,官方文档的例子相对来说太复杂了,很难让新手马上抓住重点)

(官方的例子正统且联系业务,不同类型的操作或数据放在不同文件中,很规范,但也很绕,所以本文使用的例子非常简单,且直接放在一个文件中 以便于理解)

 

搭飞机前往:

Flux思想Redux基本概念Redux的使用Redux在React中的使用(同步)Redux在React中的使用(异步,使用中间件)

 

一、Flux

Flux是一种概念思想,或者说是一种应用架构

根据它的概念,一个应用中的数据流动应是单向的,且应用中的所有数据保存在一个位置,数据变化时保证视图也同步变化,保证了数据和视图的状态是一一对应起来的

此应用应该分为四层:

  • view层:应用的视图,页面的(数据)展示
  • action层:(视图)发出的某些动作,比如点击事件
  • dispatcher层:派发器,接收action并处理这些动作,更新数据
  • store层:存放应用的数据,数据更新后,提醒view层更新视图

Flux --> Redux --> Redux React 基础实例教程

它的概念思想可能一时半会理解不了,没关系,过段时间就好了

 

二、Redux

上面说到,Flux只是一个思想,我们可以根据这个思想来自己实现出一个技术方案,来解决问题

是要解决什么问题呢?

在使用React的过程中,在组件间通信的处理上我们用了回调的方式,如果组件层级很深,不同组件间的数据交流就会导致回调及其触发的函数非常多,代码冗杂

需要一个状态管理方案,方便管理不同组件间的数据,及时地更新数据

而Flux思想中的Store层,切合了这个问题

 

1. 什么是Redux

Redux是受Flux启发实现的一个技术方案,可以认为它是Flux的产物,但它并没有沿用Flux所有的思想

主要区别是Flux的派发器dispatcher,Redux认为使用派发器就得增加事件订阅/发布的规则,倒不如直接用函数调用的方式来得实在,简单而统一,所以就将处理action的任务交给了store层(直接调用这个对象的dispatch方法)

2. 什么时候用Redux

Redux说简单简单,因为也就几个API,理解好概念就好用了;说复杂也复杂,因为它将一个应用分成了不同部分(action、处理action、store数据等),在正规的项目中是推荐将各部分区分到不同文件中的(如官方的例子),文件数量很多可能会比较难管理,当然,细粒化了也就减少了耦合度。最后还要加个操作把Redux的数据更新给React组件(如果用了React)

在大多数情况下,Redux是不需要用的,如UI层非常简单,没有太多互动的

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

 

而在多交互,多数据源的时候可以考虑使用

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

在需要管理复杂组件状态的时候,可以考虑使用

  • 某个组件的状态,需要共享某个状态
  • 需要在任何地方都可以拿到一个组件
  • 需要改变全局状态一个组件
  • 需要改变另一个组件的状态

 

3. 开始用Redux

上面讲了那么多字,还是看代码来得实在

这里先纯粹讲Redux,毕竟它和React是没啥关系的

首先是环境配置,基本上都会使用ES6,所以Babel的支持是必须的

然后是Redux的支持,如果使用Webpack打包编译,就用npm安装个redux包

这里采用直接在浏览器引入的方式,使用 这个库

  <body>
        <div id="box"></div>

        <script type="text/javascript" src="../lib/react.js"></script>
        <script type="text/javascript" src="../lib/react-dom.js"></script>
        <script type="text/javascript" src="../lib/redux.min.js"></script>
        <script type="text/javascript" src="../build/reduxStart.js"></script>
    </body>

最后build里的为demo代码用babel编译之后的es5文件

在全局之中有Redux这个对象,取其中的几个属性来用

let {Component} = React;
let {render} = ReactDOM;
let {createStore, combineReducers} = Redux;

3.1 Redux需要一个store来存放数据

这个store就由createStore创建

3.2 需要定义各个操作是什么,即action

通常来说它是一个对象,包含type属性表示是什么操作,以及其他属性携带一些数据

它可能长这样子,建议是遵循官方的 一些规范

let upAction = {
    type: 'UP'
};

我们不止会传type,还会传一些值,如果传不同的值就let一次就太冗杂了,一般来说就会用一个方法代替

let upAction = function(value) {
    return {
        type: 'up',
        value
    };
};

3.3 需要定义怎么处理操作,在redux中它被称作reducer

为什么把这种操作称作reducer呢

redux引入了JS数组reduce方法的思想,JS的reduce长这样

var arr = [1, 2, 3, 4];

var num = arr.reduce((a, b) => {
    return a + b;
});

num // 10

var num = arr.reduce((a, b) => {
    return a + b;
}, 5);

num // 15

当然了,只是看起来像,实际上差别挺大的,redux的reducer看起来像这样

let upReducer = function(state = 0, action) {
    switch (action.type) {
        case 'up':
            return state + action.value;
        default:
            return state;
    }
};

它是一个函数,接收两个参数,第一个参数为数据(即某个状态state),第二个参数为action操作对象

为了切合store中数据与view中视图是一一对应的,reducer规定需始终返回新的state数据,不能直接在原有state中修改

并且,建议在匹配不到action的时候始终返回默认的state状态,且建议在第一个参数中初始化默认的state值

 

3.4 在创建store的时候绑定reducer

redux基本上把所有操作都给了store,所以大部分方法都是用store来调用的

其实,你也可以认为Flux中的派发器(dispatcher)就是在里面自动绑定的

let store = createStore(reducer);

// let store = createStore(reducer, 10);

如上,创建store的时候传入reducer,可以接收第二个参数表示reducer使用的默认值

3.5 视图发出action动作

在某个时刻,发出了这些动作

store.dispatch(upAction(10));
store.dispatch(upAction(100));

3.6 使用store.getState()获取store中的数据

3.7 动作发出后,reducer匹配动作更新store中的数据,视图view层使用subscribe监听数据的改变

store.subscribe(() => console.log(store.getState()));

 

来看一下完整的代码

let {Component} = React;
let {render} = ReactDOM;
let {createStore, combineReducers} = Redux;

let upAction = function(value) {
    return {
        type: 'up',
        value
    };
}

let upReducer = function(state = 0, action) {
    switch (action.type) {
        case 'up':
            return state + action.value;
        default:
            return state;
    }
};

let store = createStore(upReducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());

console.log(store.getState());

store.subscribe(() => console.log(store.getState()));

store.dispatch(upAction(10));
store.dispatch(upAction(100));

注意上面createStore中第二个参数是用于Redux DevTool的配置,即这个东西

Flux --> Redux --> Redux React 基础实例教程

使用这个工具可以便于开发

看看上面代码的输出

Flux --> Redux --> Redux React 基础实例教程

初始获取到的值为0,两次action后分别更新相关的数据状态。如果加上初始默认值10

let store = createStore(upReducer, 10, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());

Flux --> Redux --> Redux React 基础实例教程

 

3.8 使用多个reducer时,使用Redux的combineReducers方法

action当然不会只是up,可能是down,这时可以直接用switch语句切换;但如果action不是这里增减的操作,放在一起就有点乱套了

所以需要定义多个reducer,但createStore方法只接收一个reducer,所以就需要整合多个reducer为一个,再统一传入

它看起来像这样

let reducer = combineReducers({upReducer, downReducer});
// let reducer = combineReducers({
//     upReducer: upReducer, 
//     downReducer: downReducer
// });

接收一个reducer组成的对象,属性表示该reducer对应的state名(如state.upReducer),值表示这个reducer

当然,这个方法我们可以自己定义,看起来是这样

let myCombineReducers = function(reducerObj) {
    let newState = {};

    return function(state = {}, action) {
        for (let item in reducerObj) {
            newState[item] = reducerObj[item](state[item], action);
        }

        return newState;
    }
};

其实就是遍历reducer组,返回一个统一的新的reducer,且新的reducer中返回一个新的state

看看Redux中的实现,完整多了

 1 function combineReducers(reducers) {
 2       var reducerKeys = Object.keys(reducers);
 3       var finalReducers = {};
 4       for (var i = 0; i < reducerKeys.length; i++) {
 5         var key = reducerKeys[i];
 6 
 7         if (true) {
 8           if (typeof reducers[key] === 'undefined') {
 9             (0, _warning2['default'])('No reducer provided for key "' + key + '"');
10           }
11         }
12 
13         if (typeof reducers[key] === 'function') {
14           finalReducers[key] = reducers[key];
15         }
16       }
17       var finalReducerKeys = Object.keys(finalReducers);
18 
19       if (true) {
20         var unexpectedKeyCache = {};
21       }
22 
23       var sanityError;
24       try {
25         assertReducerSanity(finalReducers);
26       } catch (e) {
27         sanityError = e;
28       }
29 
30       return function combination() {
31         var state = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
32         var action = arguments[1];
33 
34         if (sanityError) {
35           throw sanityError;
36         }
37 
38         if (true) {
39           var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache);
40           if (warningMessage) {
41             (0, _warning2['default'])(warningMessage);
42           }
43         }
44 
45         var hasChanged = false;
46         var nextState = {};
47         for (var i = 0; i < finalReducerKeys.length; i++) {
48           var key = finalReducerKeys[i];
49           var reducer = finalReducers[key];
50           var previousStateForKey = state[key];
51           var nextStateForKey = reducer(previousStateForKey, action);
52           if (typeof nextStateForKey === 'undefined') {
53             var errorMessage = getUndefinedStateErrorMessage(key, action);
54             throw new Error(errorMessage);
55           }
56           nextState[key] = nextStateForKey;
57           hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
58         }
59         return hasChanged ? nextState : state;
60       };
61     }
View Code

相关文章: