为什么需要Redux
- 随着 JavaScript 单页应用日趋复杂,需要管理的状态或事件处理函数也越来越多,而且逐渐难以管理
- 其中包括服务器返回的数据、缓存数据、用户操作产生的数据 等,也包括一些 UI 的状态 ,比如某些元素是否被选中,是否显示加载动效,当前分页等
管理不断变化的
state
的痛点
- 状态之间相互会存在依赖,一个状态的变化会引起另一个状态的变化,视图可能也会引起相应变化
- 当应用程序复杂时,
state
中状态的变化时机、原因、过程等,将变得非常难以控制和追踪 React
组件通信的数据流是单向的,不能自下而上传递数据,单向数据流撑起React
的数据可控性
React
的状态维护问题
React
虽然在视图层解决了DOM
的渲染过程,但无论是组件自身的state
,还是组件通信的props
传递,或是context
数据的共享,依然是留给开发者来管理React
是视图层框架,主要负责管理视图 ,state
、props
、context
如何维护最终由开发者决定
Redux
是什么?
Redux
是一个用于管理数据和 UI 状态 的 JavaScript 应用工具,提供了可预测的状态管理Redux
除了和React
一起使用之外,也能和其他框架搭配使用(如Vue
),并且体积非常小(只有2kb
)
使用
Redux
的目的:
- 为了便于开发者管理不断变化的
state
,所以需要Redux
来对state
的状态进行管理 - 使用
React
就是将状态进行集中管理,降低状态管理难度
核心理念
Redux
的三个核心理念:Store
、Action
、Reducer
- Store: 集中维护状态的仓库,用于存放数据
- Action: 普通的
JavaScript
对象,用于描述更新的类型和内容,所有数据变化必须通过派发(dispatch)action
来更新,这样可清晰得知数据如何变化,所有数据的变化都是可跟追、可预测的 - Reducer: 必须是纯函数,用于将传入的
state
和action
结合起来生成一个新的state
三大原则
保持单一数据源
- 整个应用程序的
state
被存储在一棵对象树中,并且对象树只存储在store
中 Redux
并没有强制不能创建多个store
,但是多个store
不利于数据维护- 单一数据源可让整个应用程序的
state
变得方便维护、追踪、修改
state
是只读的
- 修改
state
的唯一方法是触发action
,不要试图通过其他方式修改state
- 确保视图或网络请求都不能直接修改
state
,只能通过action
来描述该如何修改state
- 保证所有修改都被集中化处理,并且按照严格的顺序执行,不需要担心竟态问题
使用纯函数来执行修改
- 通过
reducer
将旧state
和actions
联系在一起,并且返回一个新的state
- 随着项目复杂度增加,可将
reducer
拆分成多个小的reducers
,分别操作state
的不同部分 - 所有的
reducer
都应该是纯函数,不能产生任何的副作用
使用流程
- 安装
Redux
shell
npm i redux
- 创建初始的
state
对象,用于保存状态,存放于reducer
集中管理
javascript
// reducer.js
// 初始化的数据
const initialState = {
name: 'James',
age: 38
}
function reducer(state = initialState, action) {
return state;
}
module.exports = reducer;
- 创建
store
存储state
,必须添加reducer
javascript
// store/index.js
const { createStore } = require('redux');
const reducer = require('./reducer');
// 创建store
const store = createStore(reducer);
module.exports = store;
- 创建
actionCreators
,用于生成派发的action
对象
javascript
// actionCreators.js
const { CHANGE_AGE, CHANGE_NAME } = require('./constants');
// actionCreators:用于生成action
const changeNameAction = (name) => ({
type: CHANGE_NAME, // 通常action中都会有type属性
name
})
const changeAgeAction = (age) => ({
type: CHANGE_AGE,
age
})
module.exports = {
changeNameAction,
changeAgeAction
}
javascript
// constants.js
// 用于保存不变的action.type字段
const CHANGE_AGE = 'change_age'
const CHANGE_NAME = 'change_name'
module.exports = {
CHANGE_AGE,
CHANGE_NAME
}
- 修改
reducer
中的处理代码,reducer
必须是纯函数,不能直接修改state
javascript
const { CHANGE_AGE, CHANGE_NAME } = require('./constants')
/**
* @param {Object} state store在当前保存的state
* @param {Object} action 本次需要更新的action
* @return {Object} 作为store之后存储的state
*/
function reducer(state = initialState, action) {
switch(action.type) {
// 若有新数据更新,则返回新的state
case CHANGE_NAME:
return {...state, name: action.name }
case CHANGE_AGE:
return {...state, age: action.age }
default:
// 若无数据更新,则返回之前的state
return state
}
}
- 可以在派发
action
之前,监听store
的变化,可通过store.getState()
来获取当前的state
javascript
// 订阅store
const unsubscribe = store.subscribe(() => {
console.log('订阅数据变化:',store.getState());
})
// 派发action,修改store中的数据,当调用dispatch时就会触发reducer中switch的逻辑
store.dispatch(changeNameAction('Jimmy'))
// 取消订阅
unsubscribe()
结合React使用
- 在
React
中使用store
,每个组件都有可能用到,如果在每个组件中都引入store
去进行操作,那么代码重复率和耦合度就会很高 - 使用第三方库
react-redux
,可以通过高阶组件的原理对store
的逻辑进行抽取 - 安装
react-redux
shell
npm i react-redux
react-redux
内部提供了一个Provider
组件,可以将store
共享到所有组件
jsx
import { Provider } from 'react-redux';
import store from './store';
// 将store共享到App根组件
<Provider store={store}>
<App />
</Provider>
- 在需要使用
store
的组件中,通过react-redux
内部提供的connect
函数,将store
关联起来
connect
函数接收两个参数,返回一个高阶组件
- 参数①
mapStateToProps
: 函数,用于将state
中的数据注入到组件的props
- 参数②
mapActionToProps
: 函数,用于将action
映射到组件的props
jsx
import { PureComponent } from 'react';
import { connect } from 'react-redux';
import { mulCountAction } from '../store/actionCreators';
class About extends PureComponent {
render() {
return (
<div>
<h2>About Count:{this.props.count}</h2>
<div className='button-box'>
<button onClick={e => this.props.mulCount(2)}>x2</button>
<button onClick={e => this.props.mulCount(5)}>x5</button>
<button onClick={e => this.props.mulCount(10)}>x10</button>
</div>
</div>
)
}
}
const mapStateToProps = (state) => ({
count: state.count // 将state.count映射到this.props.count
})
const mapActionToProps = (dispatch) => ({
mulCount: (count) => {
dispatch(mulCountAction(count)) // 将分发action映射到this.props.mulCount
}
})
export default connect(mapStateToProps, mapActionToProps)(About)