目录
- 在 React 中使用 Redux 解决的问题
- 在 React 项目中加入 Redux 的好处
- React + Redux 安装 Redux
- React 中 Redux 的工作流程
- React 计数器案例
- 使用 Redux
- Provide 组件
- connect 方法
- 使用 bindActionCreators 方法继续简化
- 代码重构
- 为 Action 传递参数
- Redux 弹出框
- 初始化静态内容
- 添加默认隐藏状态
- 定义操作按钮
- 衍生的问题
- 拆分合并 reducer 拆分
- 合并
- 调整组件
在 React 中使用 Redux 解决的问题
在 React 项目中未加入 Redux 时的问题
在 React 中组件通信的数据流是单向的,顶层组件可以通过 props 属性向下层组件传递数据,而下层组件不能直接向上层组件传递数据。要实现下层组件修改上层组件的数据,需要上层组件传递修改数据的方法到下层组件。当项目越来越大的时候,组件之间传递数据以及传递修改数据的方法变得越来越困难。
在 React 项目中加入 Redux 的好处
使用 Redux 管理数据,由于 Store 独立于组件,使得数据管理独立于组件,解决了组件与组件之间传递数据困难的问题。
React + Redux 安装 Redux
在 react 项目中使用 redux 要下载两个模块
npm install redux react-redux
React 中 Redux 的工作流程
在 React 中 Redux 的工作流程有些变化:
- 组件通过 dispatch 方法触发 Action
- Store 接收 Action 并将 Action 分发给 Reducer
- Reducer 根据 Action 类型对状态进行更改并将更改后的状态返回给 Store
- 组件订阅了 Store 中的状态,Store 中的状态更新会同步到组件
React 计数器案例
React 实现
创建项目安装模块
# 创建项目(React 17 版本)
npx create-react-app myapp
# 安装 redux
cd myapp
npm install redux
删掉无用的文件
├─ src
│ ├─ App.css
│ ├─ App.test.js
│ ├─ index.css
│ ├─ logo.svg
│ ├─ reportWebVitals.js
│ └─ setupTests.js
初步实现
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux'
const initialState = {
count: 0
}
function reducer (state = initialState, action) {
switch (action.type) {
case 'increment':
return {
count: state.count + 1
}
case 'decrement':
return {
count: state.count - 1
}
default:
return state
}
}
const store = createStore(reducer)
const increment = { type: 'increment' }
const decrement = { type: 'decrement' }
function Counter() {
return <div>
<button onClick={() => store.dispatch(increment)}>+</button>
<span>{store.getState().count}</span>
<button onClick={() => store.dispatch(decrement)}>-</button>
</div>
}
store.subscribe(() => {
ReactDOM.render(
<React.StrictMode>
<Counter />
</React.StrictMode>,
document.getElementById('root')
);
})
console.log(store.getState())
ReactDOM.render(
<React.StrictMode>
<Counter />
</React.StrictMode>,
document.getElementById('root')
);
使用 Redux
开发时我们会把组件写在单独的文件中,如果将 Counter 组件单独提取,就无法访问 store 对象以及一些其它的问题,所以需要使用 redux。
安装 react-redux
npm install react-redux
react-redux 用于让 react 和 redux 完美结合,它仅仅提供两个内容:
- Provider 组件
- 它可以将创建出来的 store 放到全局,允许所有组件访问
- 可以解决将组件存储在单独文件中时,访问 store
- connect 方法
- connect 方法会帮助订阅 store,当 store 中的状态发生变化,会重新渲染组件
- 解决了需要手动订阅更新组件状态
- connect 方法可以获取 store 中的状态,映射到组件的 props 属性中
- connect 方法可以获取 dispatch 方法,组件可以通过 props.dispatch访问
Provide 组件
- Provider 组件要包裹项目中所有的组件,也就是应该被放在最外层组件上
- Provider 通过 store 属性接收创建的 store 对象
connect 方法
- 首先调用 connect 方法
- 第一个参数是一个函数
- 函数接收的第一个参数,即组件中的状态 state
- 函数要返回一个对象,该对象定义的属性都会映射到组件的 props 属性中
- 第二个参数也是一个函数
- 函数接收的第一个参数,即 dispatch 方法
- 函数要返回一个对象,可以定义触发 Action 的方法,它们同样会被映射到组件的 props 属性中
- 返回一个方法
- 继续调用返回的方法,并传递当前的组件
修改代码
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux'
import Counter from './components/Counter'
import { Provider } from 'react-redux'
const initialState = {
count: 0
}
function reducer (state = initialState, action) {
switch (action.type) {
case 'increment':
return {
count: state.count + 1
}
case 'decrement':
return {
count: state.count - 1
}
default:
return state
}
}
const store = createStore(reducer)
ReactDOM.render(
// 通过 provider 组件将 store 放在了全局,供所有组件可以访问
<React.StrictMode>
<Provider store={store}>
<Counter />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
提取的 Counter 组件
// src/components/Counter.js
import { connect } from 'react-redux'
function Counter(props) {
return <div>
<button onClick={() => props.dispatch({ type: 'increment' })}>+</button>
<span>{props.count}</span>
<button onClick={() => props.dispatch({ type: 'decrement' })}>-</button>
</div>
}
const mapStateToProps = state => ({
count: state.count
})
export default connect(mapStateToProps)(Counter)
使用 connect 第二个参数简化组件
// src/components/Counter.js
import { connect } from 'react-redux'
function Counter({count, increment, decrement}) {
return <div>
<button onClick={increment}>+</button>
<span>{count}</span>
<button onClick={decrement}>-</button>
</div>
}
const mapStateToProps = state => ({
count: state.count
})
const mapDispatchToProps = dispatch => ({
increment () {
dispatch({ type: 'increment' })
},
decrement () {
dispatch({ type: 'decrement' })
}
})
export default connect(mapStateToProps, mapDispatchToProps)(Counter)
使用 bindActionCreators 方法继续简化
触发 Action 的方法 increment
和 decrement
的内容基本是一样的,redux 提供 bindActionCreators 方法生成一个函数,来简化这种重复性代码。
- 参数
- actionCreators: 对象或返回对象的函数
- key是生成函数的名称
- value是一个返回 Action 的函数
- dispatch: 需传递Store 的 dispatch 方法
- 返回一个对象,同 connect 接收的第二个参数
// src/components/Counter.js
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
function Counter({count, increment, decrement}) {
return <div>
<button onClick={increment}>+</button>
<span>{count}</span>
<button onClick={decrement}>-</button>
</div>
}
const mapStateToProps = state => ({
count: state.count
})
const mapDispatchToProps = dispatch => ({
...bindActionCreators({
increment() {
return { type: 'increment' }
},
decrement() {
return { type: 'decrement' }
}
}, dispatch)
})
export default connect(mapStateToProps, mapDispatchToProps)(Counter)
此时还没有达到简化的效果,可以将 actionCreators 提取到单独的文件中。
// src\store\actions\counter.action.js
export const increment = () => ({ type: 'increment' })
export const decrement = () => ({ type: 'decrement' })
// src/components/Counter.js
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import * as counterActions from '../store/actions/counter.action'
function Counter({count, increment, decrement}) {
return <div>
<button onClick={increment}>+</button>
<span>{count}</span>
<button onClick={decrement}>-</button>
</div>
}
const mapStateToProps = state => ({
count: state.count
})
const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(Counter)
代码重构
为了继续演示 Redux 的相关内容,将与 Redux 相关的代码从 src/index.js
中抽离出去,让项目结构更加合理。
- 将 reducer 函数抽离到一个文件中
- 将创建 store 的代码手里到一个文件中
- 将 Actions 的 type 抽象成模块中的成员,好处是:
- 编辑器有提示,避免写错
- 编辑器自动插入模块引入的代码
将 Actions 的 type 抽象成模块中的成员
// src\store\actions\counter.action.js
import { DECREMENT, INCREMENT } from "../const/counter.const"
export const increment = () => ({ type: INCREMENT })
export const decrement = () => ({ type: DECREMENT })
将 reducer 函数抽离到一个文件中
// src\store\reducers\counter.reducer.js
import { DECREMENT, INCREMENT } from "../const/counter.const"
const initialState = {
count: 0
}
function reducer(state = initialState, action) {
switch (action.type) {
case INCREMENT:
return {
count: state.count + 1
}
case DECREMENT:
return {
count: state.count - 1
}
default:
return state
}
}
export default reducer
将创建 store 的代码手里到一个文件中
// src\store\index.js
import { createStore } from 'redux'
import reducer from './reducers/counter.reducer'
export const store = createStore(reducer)
修改后的 src/index.js
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import Counter from './components/Counter'
import { Provider } from 'react-redux'
import { store } from './store'
ReactDOM.render(
// 通过 provider 组件将 store 放在了全局,供所有组件可以访问
<React.StrictMode>
<Provider store={store}>
<Counter />
</Provider>
</React.StrictMode>,
document.getElementById('root')
)
为 Action 传递参数
当前计数器对数字进行递增/减的数值为 1,现在通过给 Action 传递参数,允许自定义数值。
- 传递参数:调用触发 Action 函数的时候传递参数
- 接收参数:返回 Action 的函数接收参数,添加到返回的 Action 对象中
- 处理参数:reducer 函数处理的时候,可以从 action 对象获取这个参数,进行处理
传递参数:
// src/components/Counter.js
function Counter({ count, increment, decrement }) {
// 修改 Counter 组件中调用 increment decrement 函数的地方
return (
<div>
<button onClick={() => increment(5)}>+</button>
<span>{count}</span>
<button onClick={() => decrement(5)}>-</button>
</div>
)
}
接收参数:
// src\store\actions\counter.action.js
import { DECREMENT, INCREMENT } from "../const/counter.const"
export const increment = payload => ({ type: INCREMENT, payload })
export const decrement = payload => ({ type: DECREMENT, payload })
处理参数:
// src\store\reducers\counter.reducer.js
import { DECREMENT, INCREMENT } from "../const/counter.const"
const initialState = {
count: 0
}
function reducer(state = initialState, action) {
switch (action.type) {
case INCREMENT:
return {
count: state.count + action.payload
}
case DECREMENT:
return {
count: state.count - action.payload
}
default:
return state
}
}
export default reducer
Redux 弹出框
在页面中显示两个按钮:
- 显示:显示弹出框
- 隐藏:隐藏弹出框
初始化静态内容
Modal 组件
// src\components\Modal.js
function Modal() {
const styles = {
width: 200,
height: 200,
position: 'absolute',
left: '50%',
top: '50%',
marginLeft: -100,
marginTop: -100,
backgroundColor: 'skyblue'
}
return (
<div>
<button>显示</button>
<button>隐藏</button>
<div style={styles}></div>
</div>
)
}
export default Modal
修改 src/index.js
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import { Provider } from 'react-redux'
import { store } from './store'
ReactDOM.render(
// 通过 provider 组件将 store 放在了全局,供所有组件可以访问
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
)
修改 src/App.js
// src\App.js
import Counter from './components/Counter'
import Modal from './components/Modal'
function App() {
return (
<div>
<Counter />
<Modal />
</div>
)
}
export default App
添加默认隐藏状态
在 reducer 中添加显示状态的属性
// src\store\reducers\counter.reducer.js
import { DECREMENT, INCREMENT } from "../const/counter.const"
const initialState = {
count: 0,
showStatus: false
}
function reducer(state = initialState, action) {...}
export default reducer
在组件中使用状态
// src\components\Modal.js
import { connect } from 'react-redux'
function Modal({ showStatus }) {
const styles = {
width: 200,
height: 200,
position: 'absolute',
left: '50%',
top: '50%',
marginLeft: -100,
marginTop: -100,
backgroundColor: 'skyblue',
display: showStatus ? 'block' : 'none'
}
return (
<div>
<button>显示</button>
<button>隐藏</button>
<div style={styles}></div>
</div>
)
}
const mapStateToProps = state => ({
showStatus: state.showStatus
})
export default connect(mapStateToProps)(Modal)
定义操作按钮
定义 Action 的 type
// src\store\const\modal.const.js
export const SHOWMODAL = 'showModal'
export const HIDEMODAL = 'hideModal'
定义生成 Action 的函数
// src\store\actions\modal.actions.js
import { HIDEMODAL, SHOWMODAL } from "../const/modal.const"
export const show = () => ({ type: SHOWMODAL })
export const hide = () => ({ type: HIDEMODAL })
创建触发 Action 的方法并使用
// src\components\Modal.js
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import * as modalActions from '../store/actions/modal.actions'
function Modal({ showStatus, show, hide }) {
const styles = {
width: 200,
height: 200,
position: 'absolute',
left: '50%',
top: '50%',
marginLeft: -100,
marginTop: -100,
backgroundColor: 'skyblue',
display: showStatus ? 'block' : 'none'
}
return (
<div>
<button onClick={show}>显示</button>
<button onClick={hide}>隐藏</button>
<div style={styles}></div>
</div>
)
}
const mapStateToProps = state => ({
showStatus: state.showStatus
})
const mapDispatchToProps = dispatch => bindActionCreators(modalActions, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(Modal)
修改 reducer 函数,处理变更:
// src\store\reducers\counter.reducer.js
import { DECREMENT, INCREMENT } from '../const/counter.const'
import { HIDEMODAL, SHOWMODAL } from '../const/modal.const'
const initialState = {
count: 0,
showStatus: false
}
function reducer(state = initialState, action) {
switch (action.type) {
case INCREMENT:
// reducer 返回的对象会替换 store 中的状态对象,所以要将其它状态也包含进去
return {
...state,
count: state.count + action.payload
}
case DECREMENT:
return {
...state,
count: state.count - action.payload
}
case SHOWMODAL:
return {
...state,
showStatus: true
}
case HIDEMODAL:
return {
...state,
showStatus: false
}
default:
return state
}
}
export default reducer
注意:reducer 返回的对象会替换 store 中的状态对象,所以要将其它状态也包含进去
衍生的问题
在 reducer 函数中匹配了所有状态的变更,当项目越来越大,状态越来越多时,管理起来就很麻烦。
所以要将 rreducer 函数进行拆分。
拆分合并 reducer 拆分
将 modal 拆分出去
// src\store\reducers\modal.reducer.js
import { HIDEMODAL, SHOWMODAL } from '../const/modal.const'
const initialState = {
show: false
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case SHOWMODAL:
return {
...state,
showStatus: true
}
case HIDEMODAL:
return {
...state,
showStatus: false
}
default:
return state
}
}
export default reducer
// src\store\reducers\counter.reducer.js
import { DECREMENT, INCREMENT } from '../const/counter.const'
const initialState = {
count: 0,
}
function reducer(state = initialState, action) {
switch (action.type) {
case INCREMENT:
// reducer 返回的对象会替换 store 中的状态对象,所以要将其它状态也包含进去
return {
...state,
count: state.count + action.payload
}
case DECREMENT:
return {
...state,
count: state.count - action.payload
}
default:
return state
}
}
export default reducer
合并
合并 reducer 需要借助 redux 提供的 combineReducers
方法。
combineReducers 把一个由多个不同 reducer 函数作为 value 的 object 对象,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore 方法。
合并后的 reducer 可以调用各个子 reducer,并把它们返回的结果合并成一个 state 对象。
由 combineReducers() 返回的 state 对象,会将传入的每个 reducer 返回的 state 按传递给 combineReducers() 时对应的 key 进行命名。
// src\store\reducers\root.reducer.js
import { combineReducers } from 'redux'
import CounterReducer from './counter.reducer'
import ModalReducer from './modal.reducer'
// 合并后的 store 为 { counter: { count: 0 }, modal: { showStatus: false } }
export default combineReducers({
counter: CounterReducer,
modal: ModalReducer
})
// src\store\index.js
import { createStore } from 'redux'
import RootReducer from './reducers/root.reducer'
export const store = createStore(RootReducer)
调整组件
因为 store 中的数据结构发生变化,所以还需要调整下组件中获取状态的地方
// src\store\index.js
import { createStore } from 'redux'
import RootReducer from './reducers/root.reducer'
export const store = createStore(RootReducer)
// src\components\Modal.js
const mapStateToProps = state => ({
showStatus: state.modal.showStatus
})