Open li-jia-nan opened 1 year ago
众所周知,如果你想了解一个技术,就去他的官网去看,准没错,我们先看一下react的官网,再看一下redux的官网:
react官网的描述:用于构建用户界面的 JavaScript 库
用于构建用户界面的 JavaScript 库
redux官网的描述:Redux 是 JavaScript 应用程序的状态容器,提供可预测的状态管理
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架构!
以上就是redux的原理
一句话概括:redux就是数据仓库,把数据统一保存起来,在隔离了数据与ui的同时,负责处理他们的绑定关系
那么什么时候需要用到redux呢?
总而言之,我们使用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架构还是相当复杂的,但是这张架构图可不是劝退指南,对于第一次听到这么多陌生名词的同学,请不要慌,接下来我们简化图中的流程,只保留store,action,reducer,component这四个部分,然后得到下面这张图,这样看起来就更清晰一些了:
store
action
reducer
component
store中保存着的就是全局数据,对于一个使用redux架构的项目来说,有且只有一个store(唯一性),我们可以把store看做一个带有推送功能的数据仓库,我们可以借用微信的朋友圈来理解这个概念:store代表你的朋友圈,其中包含了各种好友的信息,component代表自己的微信,假设你加了某人好友,然后他发了朋友圈,那么他的朋友圈状态就会马上推送到你的微信里,加好友就好比数据订阅,发朋友圈就好比数据推送,那么reducer是什么东西呢?理论上来说,它就是帮助store处理数据的方法,请注意,reducer是个方法,是个过程,是个函数,而不是具体存在的对象,它可以帮助store初始化数据、修改数据、删除数据,但是,我们为什么要使用这么麻烦的方式来处理数据,而不是直接在store中进行操作呢?回到刚才的例子中,比如你看到好友发了条朋友圈,可是你看到朋友圈里有一个错别字,请问你可以直接在自己的微信里修改别人发的朋友圈的内容吗?答案当然是不行,你没有权限修改别人的朋友圈,我们的store就是这样的道理,任何ui级别的组件,都没有权限修改store中的数据,根据redux数据单向流动的原则,数据是只读不能写的,再次回到刚才的例子中,假如朋友圈中这条信息的错别字要修改掉,我们应该怎么做呢?当然要通知朋友,并且指导他,让他自己去修改这条信息,这个通知朋友的指令,就是action,通知朋友的过程,就是dispatch,你的朋友自己修改朋友圈的过程,就是reducer,最后,当你的朋友修改好朋友圈,微信会把修改后的消息重新推送给你,这个整个过程,就是订阅与推送!
我们来归纳总结一下:
了解的差不多了,接下来我们进行代码实战,深入redux的使用中去了解它:
首先,我们实现一个计数器的功能,点击加号,数字加1,点击减号,数字减1
注意,在实战之前,我们需要先了解redux的四个核心函数:
//初始化store const store = createStore(reducers); //获取 state store.getState(); // 触发 action,请求修改数据 store.dispatch(action); // 订阅数据变化的回调函数 store.subscribe(() => {});
我们新建一个index.js文件,安装redux,代码如下:
import { createStore } from 'redux'; const store = createStore(); export default store;
新建一个actions.js文件,专门用来存放action,因为我们的功能有两个操作,加和减,所以我们需要创建两个action:
export const addAction = { type: 'add' }; export const subAction = { type: 'sub' };
新建一个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中了就好了,很简单,只需要修改两个地方即可:
// 修改actions,本来是普通对象,现在改成函数,返回一个对象: export const addAction = step => ({ type: 'add', number: step, }); export const subAction = step => ({ type: 'sub', number: step, });
// 调用的时候action变成了函数,并且需要传入一个参数 // 点击加号,调用dispatch,数字加10 addBtn.onclick = () => store.dispatch(addAction(10)); // 点击减号,调用dispatch,数字减666 subBtn.onclick = () => store.dispatch(subAction(666));
OK,这样就实现了携带参数的操作!
以便大家更好的理解,上面没有借助 Vue 或者 React 去使用,而是完全独立的写在原生js里,下面我们把它嵌入到react里面:
首先带着问题进入阅读,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架构!
以上就是redux的原理
一句话概括:redux就是数据仓库,把数据统一保存起来,在隔离了数据与ui的同时,负责处理他们的绑定关系
那么什么时候需要用到redux呢?
总而言之,我们使用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架构还是相当复杂的,但是这张架构图可不是劝退指南,对于第一次听到这么多陌生名词的同学,请不要慌,接下来我们简化图中的流程,只保留
store
,action
,reducer
,component
这四个部分,然后得到下面这张图,这样看起来就更清晰一些了:store中保存着的就是全局数据,对于一个使用redux架构的项目来说,有且只有一个store(唯一性),我们可以把store看做一个带有推送功能的数据仓库,我们可以借用微信的朋友圈来理解这个概念:store代表你的朋友圈,其中包含了各种好友的信息,component代表自己的微信,假设你加了某人好友,然后他发了朋友圈,那么他的朋友圈状态就会马上推送到你的微信里,加好友就好比数据订阅,发朋友圈就好比数据推送,那么reducer是什么东西呢?理论上来说,它就是帮助store处理数据的方法,请注意,reducer是个方法,是个过程,是个函数,而不是具体存在的对象,它可以帮助store初始化数据、修改数据、删除数据,但是,我们为什么要使用这么麻烦的方式来处理数据,而不是直接在store中进行操作呢?回到刚才的例子中,比如你看到好友发了条朋友圈,可是你看到朋友圈里有一个错别字,请问你可以直接在自己的微信里修改别人发的朋友圈的内容吗?答案当然是不行,你没有权限修改别人的朋友圈,我们的store就是这样的道理,任何ui级别的组件,都没有权限修改store中的数据,根据redux数据单向流动的原则,数据是只读不能写的,再次回到刚才的例子中,假如朋友圈中这条信息的错别字要修改掉,我们应该怎么做呢?当然要通知朋友,并且指导他,让他自己去修改这条信息,这个通知朋友的指令,就是action,通知朋友的过程,就是dispatch,你的朋友自己修改朋友圈的过程,就是reducer,最后,当你的朋友修改好朋友圈,微信会把修改后的消息重新推送给你,这个整个过程,就是订阅与推送!
我们来归纳总结一下:
了解的差不多了,接下来我们进行代码实战,深入redux的使用中去了解它:
首先,我们实现一个计数器的功能,点击加号,数字加1,点击减号,数字减1
注意,在实战之前,我们需要先了解redux的四个核心函数:
第一步,创建仓库:createStore
我们新建一个index.js文件,安装redux,代码如下:
第二步,创建指令:Action
新建一个actions.js文件,专门用来存放action,因为我们的功能有两个操作,加和减,所以我们需要创建两个action:
第三步,创建reducer
新建一个reducer.js文件,专门用来存放reducer,在创建reducer之前,我们需要初始化数据,这里我们默认值为0:
reducer创建好之后,记得传入createStore:
第四步,完成业务代码
创建index.html文件,内容如下:
创建index.js文件,内容如下:
打开浏览器,点击按钮,可以看到,加减操作都是没有任何问题的!
好了,这样我们就成功了使用redux完成了一个简单的计数器功能!
在上面实现的过程中,没有任何参数,默认步长是1,但是请大家思考一下:假如我们要加、或者要减的值不是1,而是一个任意值的时候,应该怎么实现呢?这时候,就需要携带参数了,具体怎么实现呢?把参数写在action中了就好了,很简单,只需要修改两个地方即可:
OK,这样就实现了携带参数的操作!
第五步,嵌入React
以便大家更好的理解,上面没有借助 Vue 或者 React 去使用,而是完全独立的写在原生js里,下面我们把它嵌入到react里面: