手把手教你使用Redux,猩猩都能看懂的教程

3,614 阅读10分钟
  • 首先带着问题进入阅读,redux是什么?为什么会出现redux?它的出现解决了什么?

众所周知,如果你想了解一个技术,就去他的官网去看,准没错,我们先看一下react的官网,再看一下redux的官网:

  • 可以看到:

react官网的描述:用于构建用户界面的 JavaScript 库

redux官网的描述:Redux 是 JavaScript 应用程序的状态容器,提供可预测的状态管理

那么二者到底是什么关系呢?我们知道,虽然React实际上只是一个UI框架(甚至算不上一个框架,只是一个库),不过它渲染ui的方法很别致,通过jsx生成动态vdom渲染UI,这与传统方式大相径庭,它没有架构、没有模板、没有路由、没有设计模式、也没有数据管理,也就是说,React除了渲染ui以外,其它啥都干不了。

但是,对于一个大型的、复杂的网站来说,设计模式数据管理缺一不可,因此,如果我们只使用react是没有办法开发大型项目的,我们举个例子,看下面这张图:

先观察上面左边的图片,它代表我们网站的react组件的结构,其中每一个节点就代表每一个组件,绿色的节点代表需要传递的数据

假设我们的网站就是通过这样组件树的形式渲染UI的,我们知道,在react中,数据的流动是单向的,而且总是自上而下传递的,我们可以通过props把数据从父组件传递给子组件,但是,如果我们想把下面绿色节点中的某个数据传递到最顶层的节点,这个时候,不同的组件之间该怎么通信呢?

遵循react原则,我们是没有办法直接传递数据的,但是我们可以通过函数回调的方式,通过调用父组件的函数,一层一层向上传递数据,实际上,在中大型项目中,类似这样需要共享数据的的情况非常常见,如果我们通过回调函数这样的方式一层一层同步数据,你会发现,整个网站的代码都会变的非常恶心,最后基本上会变成无法维护的状态,而且这种处理数据通信的方式,开销是非常巨大的,一个不小心,还可能陷入无限死循环中,所以在中大型项目中,我们就需要设计模式了。

关于设计模式,可能大家都知道MVC、MVVM、MV* 等……但是针对React,我们还可以使用一种更加符合react设计思想的架构模式,这就是redux,redux是一种设计模式,同时也是项目的一种架构方案,它不依赖任何库或者任何框架,它不仅在 React 中可以使用,甚至在 Vue 或者 Angular 中也可以使用,当然了,它在 Vue 或者 Angular 中使用的情况是非常少见的,因为 Vue 和 Angular 都有自己的数据框架(比如 Vue 的 vuex,还有 Angular 的 Observable)

再观察上面右边的图片,同样,它代表我们网站的react组件的结构,不同的是,所有的组件都不会直接通信了,而且数据全部放在一个叫做store的仓库中,这就是redux架构!

  • 剥离组件数据(store)
  • 数据统一存放在store中
  • 组件订阅获得数据
  • store同步推送数据更新

以上就是redux的原理

一句话概括:redux就是数据仓库,把数据统一保存起来,在隔离了数据与ui的同时,负责处理他们的绑定关系

那么什么时候需要用到redux呢?

  • 组件需要共享数据(或者叫做state)的时候
  • 某个状态需要在任何地方都可以被随时访问的时候
  • 某个组件需要改变另一个组件的状态的时候
  • 比如语言切换、皮肤主题切换、用户登录状态、登录数据共享……

总而言之,我们使用redux的目的,就是为了让数据变的可控、可预测,那么在真实项目中的工作流是怎样的呢?看下面这张图:

一般来说,使用redux,都会创建一个用来存放数据的仓库,就是上图中的Store,在这个store中,有一个或者多个reducer,然后我们还需要若干个react组件来渲染UI,除此之外,我们还需要若干个与reducer相对应的Action指令,如上图所示,store中的reducer组合在一起,就形成了项目的数据仓库,redux称之为state(我们简单理解为数据就可以了),react组件可以通过订阅Store来获得数据,然后使用数据渲染UI,展示给用户,反之,用户会通过UI中的交互修改数据,但是,react中数据的流动是单向的,所以UI组件不可以直接修改store中的数据,所以UI必须通过向store发送Action指令的方式,让store自己修改自己,而这个指令的分发过程,就叫做dispatch,当action指令到达store之后,可能会先经过若干个中间件进行数据的预处理,比如对数据的异步处理,就是在这里进行的,预处理完成之后,数据就会连同action一起传递给reduce,reduce会按照action中描述的指令来更新数据state,当state更新好之后,store就会马上把新的数据推送给订阅了自己的组件,然后组件会重新渲染UI,反馈给用户

可以看到,在实际工作中,redux架构还是相当复杂的,但是这张架构图可不是劝退指南,对于第一次听到这么多陌生名词的同学,请不要慌,接下来我们简化图中的流程,只保留storeactionreducercomponent这四个部分,然后得到下面这张图,这样看起来就更清晰一些了:

store中保存着的就是全局数据,对于一个使用redux架构的项目来说,有且只有一个store(唯一性),我们可以把store看做一个带有推送功能的数据仓库,我们可以借用微信的朋友圈来理解这个概念:store代表你的朋友圈,其中包含了各种好友的信息,component代表自己的微信,假设你加了某人好友,然后他发了朋友圈,那么他的朋友圈状态就会马上推送到你的微信里,加好友就好比数据订阅,发朋友圈就好比数据推送,那么reducer是什么东西呢?理论上来说,它就是帮助store处理数据的方法,请注意,reducer是个方法,是个过程,是个函数,而不是具体存在的对象,它可以帮助store初始化数据、修改数据、删除数据,但是,我们为什么要使用这么麻烦的方式来处理数据,而不是直接在store中进行操作呢?回到刚才的例子中,比如你看到好友发了条朋友圈,可是你看到朋友圈里有一个错别字,请问你可以直接在自己的微信里修改别人发的朋友圈的内容吗?答案当然是不行,你没有权限修改别人的朋友圈,我们的store就是这样的道理,任何ui级别的组件,都没有权限修改store中的数据,根据redux数据单向流动的原则,数据是只读不能写的,再次回到刚才的例子中,假如朋友圈中这条信息的错别字要修改掉,我们应该怎么做呢?当然要通知朋友,并且指导他,让他自己去修改这条信息,这个通知朋友的指令,就是action,通知朋友的过程,就是dispatch,你的朋友自己修改朋友圈的过程,就是reducer,最后,当你的朋友修改好朋友圈,微信会把修改后的消息重新推送给你,这个整个过程,就是订阅与推送!

我们来归纳总结一下:

  • store:一个带有推送功能的数据仓库
  • reducer:处理数据的方法,可以帮助store完成对数据的各种操作
  • action:数据更新的指令,它会告诉reduce如何去处理数据

了解的差不多了,接下来我们进行代码实战,深入redux的使用中去了解它:

首先,我们实现一个计数器的功能,点击加号,数字加1,点击减号,数字减1

注意,在实战之前,我们需要先了解redux的四个核心函数:

//初始化store
const store = createStore(reducers);

//获取 state
store.getState();

// 触发 action,请求修改数据
store.dispatch(action);

// 订阅数据变化的回调函数
store.subscribe(() => {});

第一步,创建仓库:createStore

我们新建一个index.js文件,安装redux,代码如下:

import { createStore } from 'redux';

const store = createStore();

export default store;

第二步,创建指令:Action

新建一个actions.js文件,专门用来存放action,因为我们的功能有两个操作,加和减,所以我们需要创建两个action:

export const addAction = { type: 'add' };
export const subAction = { type: 'sub' };

第三步,创建reducer

新建一个reducer.js文件,专门用来存放reducer,在创建reducer之前,我们需要初始化数据,这里我们默认值为0:

const defaultState = {
    number: 0,
};

const reducer = (state = defaultState, action) => {
    switch (action.type) {
        case 'add':
            return { number: state.number + 1 };
        case 'sub':
            return { number: state.number - 1 };
        default:
            return state;
    }
};

export default reducer;

reducer创建好之后,记得传入createStore:

import { createStore } from 'redux';
import reducer from './reducer';

const store = createStore(reducer);

export default store;

第四步,完成业务代码

创建index.html文件,内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <script src="./main.js"></script>
</head>
<body>
    <div id="num">0</div>
    <button id="add">点我加</button>
    <button id="sub">点我减</button>
</body>
</html>

创建index.js文件,内容如下:

import store from '../redux/store';
import { addAction, subAction } from '../redux/actions';

window.onload = () => {

    // 获取页面元素
    const numDiv = document.getElementById('num');
    const addBtn = document.getElementById('add');
    const subBtn = document.getElementById('sub');

    // 绑定事件,点击加号,调用dispatch,数字加1
    addBtn.onclick = () => store.dispatch(addAction);

    // 绑定事件,点击减号,调用dispatch,数字减1
    subBtn.onclick = () => store.dispatch(subAction);

    // 数据发生变化时,从store中获取到最新number的值,渲染到页面中
    store.subscribe(() => {
        const { number } = store.getState();
        numDiv.innerHTML = number;
        console.log(numDiv.innerHTML);
    });
};

打开浏览器,点击按钮,可以看到,加减操作都是没有任何问题的!

好了,这样我们就成功了使用redux完成了一个简单的计数器功能!

在上面实现的过程中,没有任何参数,默认步长是1,但是请大家思考一下:假如我们要加、或者要减的值不是1,而是一个任意值的时候,应该怎么实现呢?这时候,就需要携带参数了,具体怎么实现呢?把参数写在action中了就好了,很简单,只需要修改两个地方即可:

    1. 在Action对象中增加一个字段,新加的字段就是我们需要的参数
// 修改actions,本来是普通对象,现在改成函数,返回一个对象:

export const addAction = step => ({
    type: 'add',
    number: step,
});

export const subAction = step => ({
    type: 'sub',
    number: step,
});
    1. 修改业务代码:
// 调用的时候action变成了函数,并且需要传入一个参数

// 点击加号,调用dispatch,数字加10
addBtn.onclick = () => store.dispatch(addAction(10));

// 点击减号,调用dispatch,数字减666
subBtn.onclick = () => store.dispatch(subAction(666));

OK,这样就实现了携带参数的操作!

第五步,嵌入React

以便大家更好的理解,上面没有借助 Vue 或者 React 去使用,而是完全独立的写在原生js里,下面我们把它嵌入到react里面: