在 React 中组件通信的数据流是单向的,顶层组件可以通过 props 属性向下层组件传递数据,而下层组件不能直接向上层组件传递数据。要实现下层组件修改上层组件的数据,需要上层组件传递修改数据的方法到下层组件。当项目越来越大的时候,组件之间传递数据以及传递修改数据的方法变得越来越困难。
使用 Redux 管理数据,由于 Store 独立于组件,使得数据管理独立于组件,解决了组件与组件之间传递数据困难的问题。
在 react 项目中使用 redux 要下载两个模块
npm install redux react-redux
在 React 中 Redux 的工作流程有些变化:
创建项目安装模块
# 创建项目(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
{store.getState().count}
}
store.subscribe(() => {
ReactDOM.render(
,
document.getElementById('root')
);
})
console.log(store.getState())
ReactDOM.render(
,
document.getElementById('root')
);
开发时我们会把组件写在单独的文件中,如果将 Counter 组件单独提取,就无法访问 store 对象以及一些其它的问题,所以需要使用 redux。
安装 react-redux
npm install react-redux
react-redux 用于让 react 和 redux 完美结合,它仅仅提供两个内容:
props.dispatch
访问// 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 放在了全局,供所有组件可以访问
,
document.getElementById('root')
);
提取的 Counter 组件
// src/components/Counter.js
import { connect } from 'react-redux'
function Counter(props) {
return
{props.count}
}
const mapStateToProps = state => ({
count: state.count
})
export default connect(mapStateToProps)(Counter)
// src/components/Counter.js
import { connect } from 'react-redux'
function Counter({count, increment, decrement}) {
return
{count}
}
const mapStateToProps = state => ({
count: state.count
})
const mapDispatchToProps = dispatch => ({
increment () {
dispatch({ type: 'increment' })
},
decrement () {
dispatch({ type: 'decrement' })
}
})
export default connect(mapStateToProps, mapDispatchToProps)(Counter)
触发 Action 的方法 increment
和 decrement
的内容基本是一样的,redux 提供 bindActionCreators 方法生成一个函数,来简化这种重复性代码。
// src/components/Counter.js
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
function Counter({count, increment, decrement}) {
return
{count}
}
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
{count}
}
const mapStateToProps = state => ({
count: state.count
})
const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(Counter)
为了继续演示 Redux 的相关内容,将与 Redux 相关的代码从 src/index.js
中抽离出去,让项目结构更加合理。
将 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 放在了全局,供所有组件可以访问
,
document.getElementById('root')
)
当前计数器对数字进行递增/减的数值为 1,现在通过给 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
在页面中显示两个按钮:
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 (
)
}
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 放在了全局,供所有组件可以访问
,
document.getElementById('root')
)
修改 src/App.js
// src\App.js
import Counter from './components/Counter'
import Modal from './components/Modal'
function App() {
return (
)
}
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 (
)
}
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 (
)
}
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 函数进行拆分。
将 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/components/Counter.js
const mapStateToProps = state => ({
count: state.counter.count
})
// src\components\Modal.js
const mapStateToProps = state => ({
showStatus: state.modal.showStatus
})