在 React 中使用 Redux 解决的问题小结

来自:网络
时间:2023-01-05
阅读:
目录

在 React 中使用 Redux 解决的问题

在 React 项目中未加入 Redux 时的问题

在 React 中组件通信的数据流是单向的,顶层组件可以通过 props 属性向下层组件传递数据,而下层组件不能直接向上层组件传递数据。要实现下层组件修改上层组件的数据,需要上层组件传递修改数据的方法到下层组件。当项目越来越大的时候,组件之间传递数据以及传递修改数据的方法变得越来越困难。

在 React 中使用 Redux 解决的问题小结

在 React 项目中加入 Redux 的好处

使用 Redux 管理数据,由于 Store 独立于组件,使得数据管理独立于组件,解决了组件与组件之间传递数据困难的问题。

在 React 中使用 Redux 解决的问题小结

React + Redux 安装 Redux

在 react 项目中使用 redux 要下载两个模块

npm install redux react-redux

React 中 Redux 的工作流程

在 React 中 Redux 的工作流程有些变化:

  1. 组件通过 dispatch 方法触发 Action
  2. Store 接收 Action 并将 Action 分发给 Reducer
  3. Reducer 根据 Action 类型对状态进行更改并将更改后的状态返回给 Store
  4. 组件订阅了 Store 中的状态,Store 中的状态更新会同步到组件

在 React 中使用 Redux 解决的问题小结

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访问

在 React 中使用 Redux 解决的问题小结

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 的方法 incrementdecrement 的内容基本是一样的,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
})
返回顶部
顶部