React 状态管理工具 --- Redux

263 阅读4分钟

1.1 Redux 介绍

文章出处: 拉 勾 大前端 高薪训练营

JavaScript 状态容器,提供可预测化的状态管理

1.2 Redux 核心概念及流程

Store: 存储状态的容器,JavaScript 对象

View: 视图,HTML页面

Actions: 对象,描述对状态进行怎样的操作

Reducers: 函数,操作状态并返回新的状态

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Redux</title>
</head>
<body>
  <button id="minus">-</button>
  <span id="count">0</span>
  <button id="plus">+</button>

  <script src="./redux.min.js"></script>
  <script>

    // 3. 存储默认状态
    const initialState = {
      count: 0
    }

    // 2. 创建 reducer 函数
    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;
      }
    }

    // 1. 创建 store 对象
    const store = Redux.createStore(reducer);

    // 4. 定义 action 
    const increment = { type: 'increment' }
    const decrement = { type: 'decrement' }

    // 5. 获取按钮 给按钮添加点击事件
    document.getElementById('minus')
    .addEventListener('click', function () {
      // 6. 获取dispatch  触发 action 
      store.dispatch(decrement)
    })
    document.getElementById('plus')
    .addEventListener('click', function () {
      // 6. 获取dispatch  触发 action 
      store.dispatch(increment)
    })

    // 获取store {dispatch: ƒ, subscribe: ƒ, getState: ƒ, replaceReducer: ƒ, Symbol(observable): ƒ}
    console.log(store)
    // 获取 store 中存储的状态 state
    console.log(store.getState());

    // 订阅数据的变化
    store.subscribe(() => {
      console.log(store.getState())
      document.getElementById('count').innerHTML = store.getState().count
    });
  </script>
</body>
</html>

// 创建 store 对象

const store = Redux.createStore(reducer);

// 获取dispatch 触发 action

store.dispatch(increment)

// 2. 创建 reducer 函数

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;
  }
}

// 获取 store 中存储的状态 state

store.getState()

// 订阅数据的变化

store.subscribe(() => {
  console.log(store.getState())
});

2. React + Redux

2.1 在 React 中不使用 Redux 时遇到的问题

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

2.2 在 React 项目中加入 Redux 的好处

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

npm install redux react-redux

2.3 Redux 工作流程

组件通过 dispatch 方法触发 Action

Store 接受 Action 并将 Action 分发给Reducer

Reducer 根据 Action 类型对状态进行更改并将更改后的状态返回给 Store

组件订阅了 Store 中的状态, Store 中的状态更新会同步到组件

2.4 Redux 使用步骤

创建 store

// src/store/index.js
import { createStore } from 'redux'
import reducer from './reducers/counter.reducer'
export const store = createStore(reducer)

在根组件中使用store

import React from 'react';
import ReactDOM from 'react-dom';
import Counter from './components/Counter'
import { Provider } from 'react-redux'
import {store} from './store'
/**
 * react-redux
 * Provider
 * connect
 */

ReactDOM.render(
  // 通过 provider 组件,将store 放在了全局的组件可以够得着的地方
  <Provider store={store}>
    <Counter />
  </Provider>,
  document.getElementById('root')
);

创建 reducer

// src/store/reducers/counter.reducer.js
import { DECREMENT, INCREMENT } from "../count/counter.const";

const initialState = {
  count: 0
}

export default 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;
  }
}
// src/store/count/counter.const.js
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'

在组件中使用 connect 接受 store 里面的 state 和 dispatch

connect方法接受两个参数,返回一个高阶组件。

connect方法的第一个参数是mapStateToProps方法,将store中的state传递到组件的props中,mapStateToProps方法的参数是state,返回值是一个对象,会传递到组件中,写法如下:

const mapStateToProps = (state) => ({
  count: state.count,
  a: 'a', // 这里怎么定义,组件中就可以或得到一个属性
})

connect方法的第二个参数是mapDispatchToProps方法,将store中的dispatch传递到组件的props中,mapDispatchToProps方法的参数是dispatch,返回值是一个对象,对象中的方法可以使用dispatch,这个对象中的方法会传递到组件中,写法如下:

const mapDispatchToProps = (dispatch) => ({
  increment () {
    dispatch({ type: 'increment'})
  },
  decrement () {
    dispatch({ type: 'decrement' })
  }
})

此外,我们还可以通过redux中的bindActionCreators来帮我们创建action函数:

import {bindActionCreators} from 'redux'

// bindActionCreators 会返回一个对象
const mapDispatchToProps = dispatch => (
  // 解构
  ...bindActionCreators({
    increment () {
      return { type: 'increment'}
    },
    decrement () {
      return { type: 'decrement'}
    }
  }, dispatch)
)

或者写成

const mapDispatchToProps = dispatch => bindActionCreators({
    increment () {
      return { type: 'increment'}
    },
    decrement () {
      return { type: 'decrement'}
    }
  }, dispatch)

也可以将bindActionCreators的第一个参数进行抽离:

import * as counterActions from '../store/actions/counter.actions'
const mapDispatchToProps = dispatch => bindActionCreators(conterActions, dispatch)
// src/store/actions/counter.actions.js
import { DECREMENT, INCREMENT } from "../count/counter.const"

export const increment = () => ({type: INCREMENT})
export const decrement = () => ({type: DECREMENT})

connect方法接受mapStateToProps和mapDispatchToProps,返回一个高阶组件,然后传入Counter组件进行导出:

export default connect(mapStateToProps, mapDispatchToProps)(Counter)

最终组件代码如下

// src/components/Counter.js
import React from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import * as counterActions from '../store/actions/counter.actions'

function Counter ({count, increment, decrement}) {
  return (
    <div>
      <button onClick={decrement}>-</button>
      <span>{count}</span>
      <button onClick={increment}>+</button>
    </div>
  )
}

// 1. connect 会帮助我们去订阅 store,当store中的状态发生了变化后,可以帮我们重新渲染组件
// 2. connect 方法可以让我们获取 store 中的状态,将状态通过组建的props属性映射给组件
// 3. connect 方法可以让我们获取 dispatch 方法

const mapStateToProps = (state) => ({
  count: state.count,
  a: 'a', // 这里怎么定义,组件中就可以或得到一个属性
})

const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(Counter)

为 action 传递参数

传递参数

<button onClick={() => increment(5)}> + 5</button>

接受参数,传递reducer

export const increment = payload => ({type: INCREMENT, payload})
export const decrement = payload => ({type: DECREMENT, payload})

reducer根据接受收到的数据进行处理

export default 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;
  }
}

拆分reducer

store中的状态越多,reducer中的switch分支就会越多,不利于维护,需要拆分reducer,使用reducer提供的工具combineReducers合并每一个小的reducer

// src/store/reducers/root.reducer.js
import {combineReducers} from 'redux'
import CounterReducer from './counter.reducer'
import ModalReducer from './modal.reducer'

// { counter: { count: 0 }, modal: { show: false } }
export default combineReducers({
  counter: CounterReducer,
  modal: ModalReducer
})

const mapStateToProps = (state) => ({
  count: state.counter.count,
})
const mapStateToProps = state => ({
  showStatus: state.modal.show
})
// src/store/index.js
import { createStore, applyMiddleware } from 'redux'
import logger from './middlewares/logger'

createStore(reducer, applyMiddleware(
  logger
))

const logger = store => next => action => {
  console.log(store)
  console.log(action)
  next(action) // 千万别忘了调用 next(action)
}
export default logger
//如果注册多个中间件,中间件的执行顺序就是注册顺序,如:
createStore(reducer, applyMiddleware(
  logger,
  test
))
//那么执行顺序就是先执行logger中间件,再执行test中间件。
//如果中间件中的结尾不调用next(action),则整个流程就会卡在此处不会再往后执行了