React是用于构建用户界面的,帮助我们解决渲染DOM的过程
而在整个应用中会存在很多个组件,每个组件的state是由自身进行管理,包括组件定义自身的state、组件之间的通信通过props传递、使用Context实现数据共享
如果让每个组件都存储自身相关的状态,理论上来讲不会影响应用的运行,但在开发及后续维护阶段,我们将花费大量精力去查询状态的变化过程
这种情况下,如果将所有的状态进行集中管理,当需要更新状态的时候,仅需要对这个管理集中处理,而不用去关心状态是如何分发到每一个组件内部的
redux就是一个实现上述集中管理的容器,遵循三大基本原则:
注意的是,redux并不是只应用在react中,还与其他界面库一起使用,如Vue
redux要求我们把数据都放在 store公共存储空间
一个组件改变了 store 里的数据内容,其他组件就能感知到 store的变化,再来取数据,从而间接的实现了这些数据传递的功能
工作流程图如下所示:

根据流程图,可以想象,React Components 是借书的用户, Action Creactor 是借书时说的话(借什么书), Store 是图书馆管理员,Reducer 是记录本(借什么书,还什么书,在哪儿,需要查一下), state 是书籍信息
整个流程就是借书的用户需要先存在,然后需要借书,需要一句话来描述借什么书,图书馆管理员听到后需要查一下记录本,了解图书的位置,最后图书馆管理员会把这本书给到这个借书人
转换为代码是,React Components 需要获取一些数据, 然后它就告知 Store 需要获取数据,这就是就是 Action Creactor , Store 接收到之后去 Reducer 查一下, Reducer 会告诉 Store 应该给这个组件什么数据
创建一个store的公共数据区域
- import { createStore } from 'redux' // 引入一个第三方的方法
- const store = createStore() // 创建数据的公共存储区域(管理员)
还需要创建一个记录本去辅助管理数据,也就是reduecer,本质就是一个函数,接收两个参数state,action,返回state
- // 设置默认值
- const initialState = {
- counter: 0
- }
-
- const reducer = (state = initialState, action) => {
- }
然后就可以将记录本传递给store,两者建立连接。如下:
const store = createStore(reducer)
如果想要获取store里面的数据,则通过store.getState()来获取当前state
console.log(store.getState());
下面再看看如何更改store里面数据,是通过dispatch来派发action,通常action中都会有type属性,也可以携带其他的数据
- store.dispatch({
- type: "INCREMENT"
- })
-
- store.dispath({
- type: "DECREMENT"
- })
-
- store.dispatch({
- type: "ADD_NUMBER",
- number: 5
- })
下面再来看看修改reducer中的处理逻辑:
- const reducer = (state = initialState, action) => {
- switch (action.type) {
- case "INCREMENT":
- return {...state, counter: state.counter + 1};
- case "DECREMENT":
- return {...state, counter: state.counter - 1};
- case "ADD_NUMBER":
- return {...state, counter: state.counter + action.number}
- default:
- return state;
- }
- }
注意,reducer是一个纯函数,不需要直接修改state
这样派发action之后,既可以通过store.subscribe监听store的变化,如下:
- store.subscribe(() => {
- console.log(store.getState());
- })
在React项目中,会搭配react-redux进行使用
完整代码如下:
- const redux = require('redux');
-
- const initialState = {
- counter: 0
- }
-
- // 创建reducer
- const reducer = (state = initialState, action) => {
- switch (action.type) {
- case "INCREMENT":
- return {...state, counter: state.counter + 1};
- case "DECREMENT":
- return {...state, counter: state.counter - 1};
- case "ADD_NUMBER":
- return {...state, counter: state.counter + action.number}
- default:
- return state;
- }
- }
-
- // 根据reducer创建store
- const store = redux.createStore(reducer);
-
- store.subscribe(() => {
- console.log(store.getState());
- })
-
- // 修改store中的state
- store.dispatch({
- type: "INCREMENT"
- })
- // console.log(store.getState());
-
- store.dispatch({
- type: "DECREMENT"
- })
- // console.log(store.getState());
-
- store.dispatch({
- type: "ADD_NUMBER",
- number: 5
- })
- // console.log(store.getState());
中间件(Middleware)是介于应用系统和系统软件之间的一类软件,它使用系统软件所提供的基础服务(功能),衔接网络上应用系统的各个部分或不同的应用,能够达到资源共享、功能共享的目的
在上篇文章中,了解到了Redux整个工作流程,当action发出之后,reducer立即算出state,整个过程是一个同步的操作
那么如果需要支持异步操作,或者支持错误处理、日志监控,这个过程就可以用上中间件
Redux中,中间件就是放在就是在dispatch过程,在分发action进行拦截处理,如下图:

其本质上一个函数,对store.dispatch方法进行了改造,在发出 Action和执行 Reducer这两步之间,添加了其他功能
有很多优秀的redux中间件,如:
上述的中间件都需要通过applyMiddlewares进行注册,作用是将所有的中间件组成一个数组,依次执行
然后作为第二个参数传入到createStore中
- const store = createStore(
- reducer,
- applyMiddleware(thunk, logger)
- );
redux-thunk是官网推荐的异步处理中间件
默认情况下的dispatch(action),action需要是一个JavaScript的对象
redux-thunk中间件会判断你当前传进来的数据类型,如果是一个函数,将会给函数传入参数值(dispatch,getState)
所以dispatch可以写成下述函数的形式:
- const getHomeMultidataAction = () => {
- return (dispatch) => {
- axios.get("http://xxx.xx.xx.xx/test").then(res => {
- const data = res.data.data;
- dispatch(changeBannersAction(data.banner.list));
- dispatch(changeRecommendsAction(data.recommend.list));
- })
- }
- }
如果想要实现一个日志功能,则可以使用现成的redux-logger
-
- import { applyMiddleware, createStore } from 'redux';
- import createLogger from 'redux-logger';
- const logger = createLogger();
-
- const store = createStore(
- reducer,
- applyMiddleware(logger)
- );
这样我们就能简单通过中间件函数实现日志记录的信息
首先看看applyMiddlewares的源码
- export default function applyMiddleware(...middlewares) {
- return (createStore) => (reducer, preloadedState, enhancer) => {
- var store = createStore(reducer, preloadedState, enhancer);
- var dispatch = store.dispatch;
- var chain = [];
-
- var middlewareAPI = {
- getState: store.getState,
- dispatch: (action) => dispatch(action)
- };
- chain = middlewares.map(middleware => middleware(middlewareAPI));
- dispatch = compose(...chain)(store.dispatch);
-
- return {...store, dispatch}
- }
- }
所有中间件被放进了一个数组chain,然后嵌套执行,最后执行store.dispatch。可以看到,中间件内部(middlewareAPI)可以拿到getState和dispatch这两个方法
在上面的学习中,我们了解到了redux-thunk的基本使用
内部会将dispatch进行一个判断,然后执行对应操作,原理如下:
- function patchThunk(store) {
- let next = store.dispatch;
-
- function dispatchAndThunk(action) {
- if (typeof action === "function") {
- action(store.dispatch, store.getState);
- } else {
- next(action);
- }
- }
-
- store.dispatch = dispatchAndThunk;
- }
实现一个日志输出的原理也非常简单,如下:
- let next = store.dispatch;
-
- function dispatchAndLog(action) {
- console.log("dispatching:", addAction(10));
- next(addAction(5));
- console.log("新的state:", store.getState());
- }
-
- store.dispatch = dispatchAndLog;
在上面文章了解中,我们了解到redux是用于数据状态管理,而react是一个视图层面的库
如果将两者连接在一起,可以使用官方推荐react-redux库,其具有高效且灵活的特性
react-redux将组件分成:
通过redux将整个应用状态存储到store中,组件可以派发dispatch行为action给store
其他组件通过订阅store中的状态state来更新自身的视图
使用react-redux分成了两大核心:
在redux中存在一个store用于存储state,如果将这个store存放在顶层元素中,其他组件都被包裹在顶层元素之上
那么所有的组件都能够受到redux的控制,都能够获取到redux中的数据
使用方式如下:
- <Provider store = {store}>
- <App />
- <Provider>
connect方法将store上的getState和 dispatch包装成组件的props
导入conect如下:
import { connect } from "react-redux";
用法如下:
connect(mapStateToProps, mapDispatchToProps)(MyComponent)
可以传递两个参数:
mapStateToProps
mapDispatchToProps
把redux中的数据映射到react中的props中去
如下:
- const mapStateToProps = (state) => {
- return {
- // prop : state.xxx | 意思是将state中的某个数据映射到props中
- foo: state.bar
- }
- }
组件内部就能够通过props获取到store中的数据
- class Foo extends Component {
- constructor(props){
- super(props);
- }
- render(){
- return(
- // 这样子渲染的其实就是state.bar的数据了
- this.props.foo
- )
- }
- }
- Foo = connect()(Foo)
- export default Foo
将redux中的dispatch映射到组件内部的props中
- const mapDispatchToProps = (dispatch) => { // 默认传递参数就是dispatch
- return {
- onClick: () => {
- dispatch({
- type: 'increatment'
- });
- }
- };
- }
-
- class Foo extends Component {
- constructor(props){
- super(props);
- }
- render(){
- return(
-
-
- )
- }
- }
- Foo = connect()(Foo);
- export default Foo;
整体流程图大致如下所示:

可以根据项目具体情况进行选择,以下列出两种常见的组织结构
角色如下:
参考如下:
- reducers/
- todoReducer.js
- filterReducer.js
- actions/
- todoAction.js
- filterActions.js
- components/
- todoList.js
- todoItem.js
- filter.js
- containers/
- todoListContainer.js
- todoItemContainer.js
- filterContainer.js
使用redux使用功能组织项目,也就是把完成同一应用功能的代码放在一个目录下,一个应用功能包含多个角色的代码
Redux中,不同的角色就是reducer、actions和视图,而应用功能对应的就是用户界面的交互模块
参考如下:
- todoList/
- actions.js
- actionTypes.js
- index.js
- reducer.js
- views/
- components.js
- containers.js
- filter/
- actions.js
- actionTypes.js
- index.js
- reducer.js
- views/
- components.js
- container.js
每个功能模块对应一个目录,每个目录下包含同样的角色文件:
其中index模块用于导出对外的接口
导入方法如下:
import { actions, reducer, view as TodoList } from './xxxx'