前言 🤔
React
是单向数据流,数据通过props
从父节点传递到子节点。如果顶层的某个props
改变了,React
会重新渲染所有的子节点。注意⚠️:props
是只读的(即不可以使用this.props
直接修改props
),它是用于在整个组件树中传递数据和配置。- 每个组件都有属于自己的
state
,state
和props
的区别在于state
只存在于组件内部。注意 ⚠️:只能从当前组件调用this.setState
方法修改state
值(不可以直接修改this.state
)。 - 可见,更新子组件有两种方式,一种是改变子组件自身的
state
值,另一种则是更新子组件从父组件接收到的this.props
值从而达到更新。 - 在
React
项目开发过程中,我们大多时候需要让组件共享某些数据。一般来说,我们可以通过在组件间传递数据(通过props
)的方式实现数据共享,然而,当数据需要在非父子关系的组件间传递时操作起来则变得十分麻烦,而且容易让代码的可读性降低,这时候我们就需要使用state
(状态)管理工具。 - 常见的状态管理工具有
redux
,mobx
。由于redux
提供了状态管理的整个架构,并有着清晰的约束规则,适合在大型多人开发的应用中使用。本文介绍的是如何在React
项目中使用redux
进行状态管理。
进入正题 🥰
- 本节主要介绍
redux
和react-router
相关的基础知识 📖和相关配置 👩🏾💻。
redux
基本概念
redux
适用于多交互、多数据源的场景。从组件角度看,如果我们的应用有以下场景,则可以考虑在项目中使用redux
:- 某个组件的状态,需要共享
- 某个状态需要在任何地方都可以拿到
- 一个组件需要改变全局状态
- 一个组件需要改变另一个组件的状态
- 当我们的应用符合以上提到的场景时,若不使用
redux
或者其他状态管理工具,不按照一定规律处理状态的读写,项目代码的可读性将大大降低,不利于团队开发效率的提升。

- 如上图所示,
redux
通过将所有的state
集中到组件顶部,能够灵活的将所有state
各取所需地分发给所有的组件。 redux
的三大原则:- 整个应用的
state
都被存储在一棵object tree
中,并且object tree
只存在于唯一的store
中(这并不意味使用redux
就需要将所有的state
存到redux
上,组件还是可以维护自身的state
)。 state
是只读的。state
的变化,会导致视图(view
)的变化。用户接触不到state
,只能接触到视图,唯一改变state
的方式则是在视图中触发action
。action
是一个用于描述已发生事件的普通对象。- 使用
reducers
来执行state
的更新。reducers
是一个纯函数,它接受action
和当前state
作为参数,通过计算返回一个新的state
,从而实现视图的更新。
- 整个应用的

- 如上图所示,
redux
的工作流程大致如下:- 首先,用户在视图中通过
store.dispatch
方法发出action
。 - 然后,
store
自动调用reducers
,并且传入两个参数:当前state
和收到的action
。reducers
会返回新的state
。 - 最后,当
store
监听到state
的变化,就会调用监听函数,触发视图的重新渲染。
- 首先,用户在视图中通过
- 放一张图加深理解 ⚡⚡️⚡️️:
API
store
store
就是保存数据的地方,整个应用只能有一个store
。redux
提供createStore
这个函数,用来创建一个store
以存放整个应用的state
:
import { createStore } from 'redux';
const store = createStore(reducer, [preloadedState], enhancer);
- 可以看到,
createStore
接受reducer
、初始state
(可选)和增强器作为参数,返回一个新的store
对象。
state
store
对象包含所有数据。如果想得到某个时点的数据,就要对store
生成快照。这种时点的数据集合,就叫做state
。- 如果要获取当前时刻的
state
,可以通过store.getState()
方法拿到:
import { createStore } from 'redux';
const store = createStore(reducer, [preloadedState], enhancer);
const state = store.getState();
action
state
的变化,会导致视图的变化。但是,用户接触不到state
,只能接触到视图。所以,state
的变化必须是由视图发起的。action
就是视图发出的通知,通知store
此时的state
应该要发生变化了。action
是一个对象。其中的type
属性是必须的,表示action
的名称。其他属性可以自由设置,社区有一个规范可以参考:
const action = {
type: 'ADD_TODO',
payload: 'Learn Redux' // 可选属性
};
- 上面代码定义了一个名称为
ADD_TODO
的action
,它携带的数据信息是Learn Redux
。
Action Creator
view
要发送多少种消息,就会有多少种action
,如果都手写,会很麻烦。- 可以定义一个函数来生成
action
,这个函数就称作Action Creator
,如下面代码中的addTodo
函数:
const ADD_TODO = '添加 TODO';
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
const action = addTodo('Learn Redux');
redux-actions
是一个实用的库,让编写redux
状态管理变得简单起来。该库提供了createAction
方法用于创建动作创建器:
import { createAction } from "redux-actions"
export const INCREMENT = 'INCREMENT'
export const increment = createAction(INCREMENT)
- 上边代码定义一个动作
INCREMENT
, 然后通过createAction
创建了对应Action Creator
:- 调用
increment()
时就会返回{ type: 'INCREMENT' }
- 调用
increment(10)
返回{ type: 'INCREMENT', payload: 10 }
- 调用
store.dispatch()
store.dispatch()
是视图发出action
的唯一方法,该方法接受一个action
对象作为参数:
import { createStore } from 'redux';
const store = createStore(reducer, [preloadedState], enhancer);
store.dispatch({
type: 'ADD_TODO',
payload: 'Learn Redux'
});
- 结合
Action Creator
,这段代码可以改写如下:
import { createStore } from 'redux';
import { createAction } from "redux-actions"
const store = createStore(reducer, [preloadedState], enhancer);
const ADD_TODO = 'ADD_TODO';
const add_todo = createAction('ADD_TODO'); // 创建 Action Creator
store.dispatch(add_todo('Learn Redux'));
reducer
store
收到action
以后,必须给出一个新的state
,这样视图才会进行更新。state
的计算(更新)过程则是通过reducer
实现。reducer
是一个函数,它接受action
和当前state
作为参数,返回一个新的state
:
const reducer = function (state, action) {
// ...
return new_state;
};
- 为了实现调用
store.dispatch
方法时自动执行reducer
函数,需要在创建store
时将将reducer
传入createStore
方法:
import { createStore } from 'redux';
const reducer = function (state, action) {
// ...
return new_state;
};
const store = createStore(reducer);
- 上面代码中,
createStore
方法接受reducer
作为参数,生成一个新的store
。以后每当视图使用store.dispatch
发送给store
一个新的action
,就会自动调用reducer
函数,得到更新的state
。 redux-actions
提供了handleActions
方法用于处理多个action
:
// 使用方法:
// handleActions(reducerMap, defaultState)
import { handleActions } from 'redux-actions';
const initialState = {
counter: 0
};
const reducer = handleActions(
{
INCREMENT: (state, action) => ({
counter: state.counter + action.payload
}),
DECREMENT: (state, action) => ({
counter: state.counter - action.payload
})
},
initialState,
);
拆分、合并 reducer
- 前面提到,在一个
react
应用中只能有一个store
用于存放应用的state
。组件通过调用action
函数,传递数据到reducer
,reducer
根据数据更新对应的state
。 - 对于大型应用来说,应用的
state
必然十分庞大,导致reducer
的复杂度也随着变大。
拆分
- 在这个时候,就可以考虑将
reducer
拆分成多个单独的函数,让每个函数负责独立管理state
的一部分。
合并
redux
提供了combineReducers
辅助函数,可将独立分散的reducer
合并成一个最终的reducer
函数,然后在创建store
时作为createStore
的参数传入。- 我们可以根据业务需要,把所有子
reducer
放在不同的目录下,然后在在一个文件里面统一引入,最后将合并后的reducer
导出:
// src/model/reducers.ts
import { combineReducers } from 'redux';
import UI from './UI/reducers';
import user from './user/reducers';
import content from './content/reducers';
const rootReducer = combineReducers({
UI,
user,
content,
});
export default rootReducer;
中间件及异步操作
- 对
redux
而言,同步指的是当视图发出action
以后,reducer
立即算出state
(原始的redux
工作流程),而异步指的是在action
发出以后,过一段时间再执行reducer
。 - 同步通常发生在原生
redux
的工作流程中,而在大多数实际场景中,更多的是需要异步操作:action
发出以后,在进入reducer
之前需要先完成一个异步任务,比如发送ajax
请求后拿到数据后,再进入reducer
执行计算并对state
进行更新。 - 显然原生的
redux
是不支持异步操作的,这就要用到新的工具——中间件(middleware)来处理这种业务场景。从本质上来讲中间件是对store.dispatch
方法进行了拓展。 - 中间件提供位于
action
发起之后,到达reducer
之前的扩展点:即通过store.dispatch
方法发出的action
会依次经过各个中间件,最终到达reducer
。 - 我们可以利用中间件来进行日志记录(
redux-logger
)、创建崩溃报告(自己写crashReporter
)、调用异步接口(redux-saga
)或者路由(connected-react-router
)等操作。 redux
提供了一个原生的applyMiddleware
方法,它的作用是将所有中间件组成一个数组,依次执行。假如要使用redux-logger
来实现日志记录功能,用法如下:
import { applyMiddleware, createStore } from 'redux';
import createLogger from 'redux-logger';
const logger = createLogger();
const store = createStore(
reducer,
applyMiddleware(logger)
);
- 如果有多个中间件,则将中间件依次作为参数传入
applyMiddleware
方法中:
import { applyMiddleware, createStore } from 'redux';
import createLogger from 'redux-logger';
import createSagaMiddleware from 'redux-saga';
const logger = createLogger(); // 日志记录
const sagaMiddleware = createSagaMiddleware(); // 调用异步接口
let middleware = [sagaMiddleware];
middleware.push(logger);
const store = createStore(
reducer,
// 可传initial_state
applyMiddleware(...middleware)
);
- 需要注意⚠️的是:
createStore
方法可以接受整个应用的初始状态作为参数(可选),若传入初始状态,applyMiddleware
则需要作为第三个参数。- 有的中间件有次序要求,使用前要查一下文档(如
redux-logger
一定要放在最后,否则输出结果会不正确)。
react-redux
概念介绍
- 前面小节介绍的
redux
本身是一个可以结合react
,vue
,angular
甚至是原生javaScript
应用使用的状态库。 - 为了让
redux
帮我们管理react
应用的状态,需要把redux
与react
连接,官方提供了 react-redux库(这个库是可以选用的,也可以只用redux
)。 react-redux
将所有组件分成 UI 组件和容器组件两大类:- UI 组件只负责 UI 的呈现,不含有状态(
this.state
),所有数据都由this.props
提供,且不使用任何redux
的 API。 - 容器组件负责管理数据和业务逻辑,含有状态(
this.state
),可使用redux
的 API。
- UI 组件只负责 UI 的呈现,不含有状态(
- 简而言之,容器组件作为 UI 组件的父组件,负责与外部进行通信,将数据通过
props
传给 UI 组件渲染出视图。 react-redux
规定,所有的 UI 组件都由用户提供,容器组件则是由react-redux
自动生成。也就是说,用户负责视觉层,状态管理则是全部交给react-redux
。
API
connect 方法
react-redux
提供了connect
方法,用于将 UI 组件生成容器组件:
import { connect } from 'react-redux'
class Dashboard extends React.Component {
...
// 组件内部可以获取 this.props.loading 的值
}
const mapStateToProps = (state) => {
return {
loading: state.loading,
}
}
// 将通过 connect 方法自动生成的容器组件导出
export default connect(
mapStateToProps, // 可选
// mapDispatchToProps, // 可选
)(Dashboard)
- 从上面代码可以看到,
connect
方法接受两个可选参数用于定义容器组件的业务逻辑:mapStateToProps
负责输入逻辑,即将state
映射成传入 UI 组件的参数(props
)mapDispatchToProps
负责输出逻辑,即将用户对 UI 组件的操作映射成action
- 注意⚠️:当
connect
方法不传入任何参数时,生成的容器组件只可以看作是对 UI 组件做了一个单纯的包装,不含有任何的业务逻辑:- 省略
mapStateToProps
参数, UI 组件就不会订阅store
,即store
的更新不会引起 UI 组件的更新。 - 省略
mapDispatchToProps
参数, UI 组件就不会将用户的操作当作action
发送数据给store
,需要在组件中手动调用store.dispatch
方法。
- 省略
mapStateToProps
mapStateToProps
是一个函数,它的作用就是建立一个从state
对象(外部)到 UI 组件props
对象的映射关系。该函数会订阅 整个应用的store
,每当state
更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染。mapStateToProps
的第一个参数总是state
对象,还可以使用第二个参数(可选),代表容器组件的props
对象:
// 容器组件的代码
// <Dashboard showType="SHOW_ALL">
// All
// </Dashboard>
const mapStateToProps = (state, ownProps) => {
return {
active: ownProps.showType === "SHOW_ALL",
loading: state.loading,
}
}
- 使用
ownProps
作为参数后,如果容器组件的参数发生变化,也会引发 UI 组件重新渲染。
mapDispatchToProps
mapDispatchToProps
是connect
函数的第二个参数,用来建立 UI 组件的参数到store.dispatch
方法的映射。- 由于在项目中大多使用
mapDispatchToProps
比较少,这里不进行细讲。关于mapStateToProps
、mapDispatchToProps
和connect
的更详细用法说明可以查看文档。
Provider 组件
- 使用
connect
方法生成容器组件以后,需要让容器组件拿到state
对象,才能生成 UI 组件 的参数。 react-redux
提供了Provider
组件,可以让容器组件拿到state
,具体用法是需要用Provider
组件包裹项目的根组件(如App),使得根组件所有的子组件都可以默认获取到state
对象:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import { store } from './store/configureStore';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'),
);
react-router
react-router
是完整的react
的路由解决方案,它保持UI
与URL
的同步。在项目中我们使用的是最新的v4
版。- 需要注意⚠️的是,在开发中不应该直接安装
react-router
,因为👉:在v4
版中react-router
被划分为三个包:react-router
,react-router-dom
和react-router-native
,它们的区别如下:react-router
:提供核心的路由组件和函数。react-router-dom
:提供浏览器使用的路由组件和函数。react-router-native
:提供react-native
对应平台使用的路由组件和函数。
- 当我们的
react
应用同时使用了react-router
和redux
,则可以将两者进行更深度的整合,实现:- 将
router
的数据与store
进行同步,并且可以从store
访问router
数据,可使用this.props.dispatch
方法发送action
。 - 通过
dispatch actions
导航,个人理解是可使用store.dispatch(push('routerName'))
切换路由。 - 在
redux devtools
中支持路由改变的时间旅行调试。
- 将
- 想要实现以上的目标,则可以通过
connected-react-router
和history
两个库进行实现,步骤如下:- 在创建
store
的文件添加配置,包括创建history
对象、使用connected-react-router
提供的connectRouter
方法和history
对象创建root reducer
、使用connected-react-router
提供的routerMiddleware
中间件和history
对象实现dispatch actions
导航。
import { connectRouter, routerMiddleware } from 'connected-react-router'; import createHistory from 'history/createBrowserHistory'; import { createStore, applyMiddleware } from 'redux'; import { createLogger } from 'redux-logger'; import createSagaMiddleware from 'redux-saga'; import reducer from '../model/reducers'; export const history = createHistory(); const sagaMiddleware = createSagaMiddleware(); // 调用异步接口 let middleware = [sagaMiddleware, routerMiddleware(history)]; const logger = createLogger(); // 日志记录 middleware.push(logger); const initialState = {}; const store = createStore( connectRouter(history)(reducer), initialState, applyMiddleware(...middleware) );
- 在项目入口文件
index.js
中为根组件中添加配置,包括使用connected-react-router
提供的ConnectedRouter
组件包裹路由,将ConnectedRouter
组件作为Provider
的子组,并且将在store
中创建的history
对象引入,将其作为props
属性 传入ConnectedRouter
组件:
import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { Provider } from 'react-redux' import { ConnectedRouter } from 'connected-react-router' import App from './App' import rootSaga from './model/sagas'; import { store, history } from './store/configureStore'; ReactDOM.render( <Provider store={store}> <ConnectedRouter history={history}> <App /> </ConnectedRouter> </Provider>, document.getElementById('root'), );
- 在创建
- 以上则完成了
react-router
和redux
的深度整合 ✌️。
总结 👀
- 本文介绍的是如何在
React
项目中使用redux
进行状态管理,并对相关基础知识进行介绍和展示了完整的代码。 - 在进行业务代码开发前通常会对项目进行的一些特殊配置,有利于后期的工程开发,具体内容可参考 👉:react + typescript 项目的定制化过程。
以上内容如有遗漏错误,欢迎留言 ✍️指出,一起进步💪💪💪
如果觉得本文对你有帮助,🏀🏀留下你宝贵的 👍