redux懒人教程

1,483 阅读8分钟

对于没有使用过redux的同学,一来直接打开官网开始撸教程恐怕大多都对里面提到的各种概念一脸懵,而打开论坛上的各种野生教程又会发现,或许是因为redux的源码大概只有三百多行,因此论坛上写的比较优秀的教程都深入到redux的源码中,需要你花时间理清redux的原理之后你才知道这东西要怎么用。因此我尝试在此写一篇懒人教程,希望能通过比较通俗易懂的语言,以类比的方式向没有接触过redux的朋友解释一下它是干嘛用的,具体要怎么用。

下面开始我的表演。

redux要解决什么问题

作为开发者,我们在选择工具和库时,切忌"为了用而用",而应该清楚我们要用的工具是用来解决什么问题的,在我们的开发场景中是否会碰到这样的问题,用了这个工具后能不能帮我们解决问题。搞清楚这些再去学习对应的工具和库,工具才能称之为工具,否则就变成了工具是开发者的主人了。因此,在学习redux之前,首先应该了解一下为什么需要redux。 提及redux的时候,常常跟react联系在一起,但是其实redux并非是为react定制的,首先要明确,redux只是一个架构模式,它要解决的是全局状态不好管理这个问题,你可以在任何有这样的问题的场景下使用它。我们来看这样一个例子:

function showName(name) {
    console.log("user's name: ", name);
}

function showAge(age) {
    console.log("user's age: ", age);
}

window.user = {
    name: 'Bob',
    detail: {
        age: 11,
        gender: 'male'
    }
}

/* ......很多很多模块 */

showName(user.name);
showAge(user.detail.age);

假设有这样一个变量user,它存储了一个用户的个人信息,这个信息可能在代码的很多模块中被读写,为了方便, 我们把这个变量设置为全局变量。然而现在问题来了,这个user是一个在裸奔的变量,任何模块都可以轻易地修改它,假如我期望在showName的时候打印Bob,但是实际上却打印了undefined,那么在这"很多很多个模块中",究竟是谁修改了user变量,你根本没法定位,其实这也是老生常谈的尽量避免全局变量的原因。然而矛盾在于,虽然我知道定义全局变量可能会带来混乱,但是有些变量确实需要被各种不同的模块使用,我不得不把它定义成全局变量,在大型应用中,这样的全局变量可能还不少,而且数据结构还可能非常复杂。因此,在这样的场景下,我们迫切需要一个数据的管理者制定好全局变量使用规范,帮我们管理全局变量——而这就是redux做的事情。

redux是怎么解决问题的

俗话说的好,在计算机领域,如果有什么问题是加一层中间层解决不了的,那就加两层。而redux就是这个中间层,再来回顾一下我们面临的痛点:一个需要被全局共享的非常重要的变量(下文称之为状态),却可以被散落的模块肆意妄为地修改:

现在为了改变这种局面,就必须有一个“我不要你觉得,我要我觉得”的霸道总裁一把接管对状态的读写权。

首先,乱世用重典,我们需要先颁布一个《redux共和国状态管理条例》,以后大家谁要修改状态,都得写社会主义法治程序,按照条例规定好的操作来修改状态。在这部《条例》的开头,先昭告天下我们要管理的状态(state)的初始值是什么,然后,我们一条一条地列举针对这个状态,你可以进行哪些操作(action.type), 以及这个操作对状态的修改是什么。比如现在我们需要管理一个数组,它的初始值是一个空数组,我们规定对这个数组的合法操作有添加一个项,删除一个项和清空整个数组,按照刚才的思路,我们用代码颁布一个《数组状态管理条例》:

const initState = [];
const reducer = (state = initState, action) => {
    switch(action.type) {
        case 'add':
            return [...state, action.addedItem];
        case 'delete':
            return [...state.slice(0, action.deletedIndex), ...state.slice(action.deletedIndex + 1)];
        case 'clear':
            return []
        default:
            return state;
    }
}

好了,无需多言了吧,现在你已经知道redux中最重要的两个概念reducer和action是什么了,reducer正是我们刚才说的《redux共和国状态管理条例》,而action则是条例里的细则。不过或许你已经发现,虽然我们说在条例的细则中定义好了如何去修改状态, 但是,我们并没有真正地去"修改"状态。听起来似乎有点绕,拿add这个操作为例,我们并没有用

...
switch(action.type) {
    case 'add':
        state.push(action.addedItem);
        return state;
    ...
}

这种写法,而是

...
switch(action.type) {
    case 'add':
        return [...state, action.addedItem];
    ...
}

把原数组拷贝了一遍,生成了一个新数组,然后在新数组的末尾添加了一个新的项,最后再把这个新的数组返回作为状态的最新值,也就是说,我们不是直接去修改状态,而是根据旧状态,返回一个新状态。实际上,按照这种写法,我们写出的reducer就是一个纯函数,所谓纯函数需要满足两个条件:

  1. 纯函数返回的结果只取决于它的参数,而不取决于任何外部变量
  2. 纯函数内部不会去修改任何外部变量,也就是说,纯函数不会带来任何副作用

关于reducer为什么要写成纯函数,我觉得这是一个不得不结合redux源码单独写一篇文章分析的问题,但是既然都说是懒人教程,只希望你会用,至于原理可以后面又深究,现在你可以暂且把它当做霸道总裁redux的一个奇怪的小癖好。

好了,我们已经有《redux共和国状态管理条例》了,现在还缺一个条例的坚定执行者。于是我们用createStore函数,传入我们制定好的法律——reducer,它将返回一个执行法律的检察官——store。现在,对状态的读写都得经过store之手,要读取状态值,就调用store.getState(); 要修改状态,就得先学习一下法律知识,看看reducer中有哪些合法的action,然后调用store.dispatch(action)对状态进行修改; 同时, 你也可以订阅状态的改变,给store.subscribe传入一个回调函数,每一次状态改变时,都会执行一次这个回调函数——这是一个典型的观察者模式。

现在来看下面这段代码,能看懂的话说明你已经掌握redux的核心用法了, 看不懂的话可以再把刚才这个治乱世的过程再品一品

import { createStore } from 'redux';

const initState = [];
const reducer = (state = initState, action) => {
    switch(action.type) {
        case 'add':
            return [...state, action.addedItem];
        case 'delete':
            return [...state.slice(0, action.deletedIndex), ...state.slice(action.deletedIndex + 1)];
        case 'clear':
            return []
        default:
            return state;
    }
};

const store = createStore(reducer);

// 订阅状态变化
store.subscribe(() => {
    console.log(store.getState());
})

// 添加一个项
store.dispatch({
    type: 'add',
    addedItem: 1
});

// 又添加一个项
store.dispatch({
    type: 'add',
    addedItem: 2
});

删除一个项
// store.dispatch({
    type: 'delete',
    deletedIndex: 0
});

// 清空状态 
store.dispatch({
    type: 'clear'
})

或许你已经发现,使用redux一点都不难,其核心思想无非就是制定规则(reducer)和遵守规则(store.dispatch)罢了。 在这个基础上,redux总裁还想再完善一下自己制定的这套机制,以应对更加复杂的情况。试想一下,如果这个redux共和国只有30个国民,可能还没有你高中的班级人多,那么颁布一部像你们班班规一样规模的《管理条例》就绰绰有余了,但是如果这个redux共和国像我泱泱华夏一样地大物博,你就得分别颁布《redux共和国刑法》,《redux共和国婚姻法》,《redux共和国未成年人保护法》.......最终这些法律才完整组成共和国的法律体系。回到程序世界,reudx提供了combineReducer这个api,允许你在管理大型应用的状态时,分别写一些小粒度的reducer,然后用这个api把它们组合成一个总reducer之后又传给createStore。

另外,不同国家检察官执法时候的花式姿势都不太一样,例如美国警察抓犯人的时候都喜欢先说一句“你有权保持沉默,但是你说的每一句话都将成为呈堂证供”。或许你也希望在每一次store.dispatch时能在控制台打印一句“xxx状态你有权保持沉默,但是你的值现在将从xxx被改变为xxx”,那么你可以把这个例行程序通过插件机制在createStore的时候告诉你的检察官,只需要使用redux提供的applyMiddleware这个api就行。社区里已经提供了各种这样的执法前的花式姿势插件,比如redux-thunk用于方便处理异步状态变更,redux-logger用于在状态变更时打印日志等。

总结

最后,把redux的使用方式用下面这张图总结一下

很好,现在我们已经搞清楚如何用redux来把混乱的全局变量管理得井井有条的了,休息一下,或者直接进入下一篇文章 《react-redux懒人教程》我们就来看看这个霸道总裁是怎么管理你的react帝国的。