Skip to content

使用 redux 及 react-redux 进行状态管理

yarn add redux react-redux redux-thunk redux-devtools-extension

redux 核心主要是

  • reducer
  • action
  • store

使用 react-reudx 则需要将原本的组件(UI 组件),使用容器组件(content)进行包裹。 容器组件会将 redux 中保存的状态和操作状态的方法使用 Props 传递给 UI 组件。

主要的使用流程:

  1. 创建 store
  2. 创建 action 定义操作数据的动作(action)
  3. 创建 reducer 包含数据(preState)和操作数据的方法(reducer)
  4. 合并 reducer
  5. 在 Ui 组件中,使用 hooks,获取数据(useSelector)和操作数据的方法(useDispatch)

增加 redux 的使用流程

  1. 创建 action 定义操作数据的动作(action)
  2. 创建 reducer 包含数据(preState)和操作数据的方法(reducer)
  3. 合并 reducer

store.js

下面是将 redux 写成一个 js 的示例,实际项目中不会这样操作 包含 action reducer store

jsx
// store.js
import { createStore, applyMiddleware, combineReducers } from 'redux' // combineReducers用于合并所有reducer
import thunk from 'redux-thunk' // 引入redux-thunk 用于支持异步action

// 引入redux-devtools-extension 用于使用 redux 开发工具的
import { composeWithDevTools } from 'redux-devtools-extension'

// action 专门为Count组件生成action对象
const creatIncrementAction = (data) => {
  return {
    type: 'increment',
    data
  }
}

// 同步action,返回值为object的对象
const creatDecrementAction = (data) => {
  return {
    type: 'decrement',
    data
  }
}

// 所谓的异步acton,就是action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的
const creatIncrementAsyncAction = (data, time) => {
  return (dispatch, getState) => {
    setTimeout(() => {
      const p = getState() // 还能获取reducer 中的数据
      const y = getState().getIn(['abc', 'y']) // 使用了 immutable 获取reducer中定义的数据的方法
      dispatch(creatIncrementAction(data))
    }, time)
  }
}

// 异步获取数据模板
const getTopBannersAction = () => {
  return async (dispatch) => {
    const res = await getTopBannersRquest()
    dispatch(setTopBannersDataAction(res.banners))
  }
}
// ------------------------------------

// reducer
// 初始化状态
const initState = {
  count: 0
}
// 操作状态的方法
function countReducer(preState = initState, action) {
  const { data, type } = action

  switch (type) {
    case 'increment':
      // 展开对象,并重新赋值
      console.log({ ...preState })
      // 后续这种方法会弃用
      return { ...preState, count: preState.count + data }
    // 改而使用这个方法,需要安装 immutable,
    //  preState.set('count',preState.count + data)
    case 'decrement':
      return { ...preState, count: preState.count - data }
    default:
      return preState
  }
}

// 其他的Reducer
function otherReducer(preState, action) {
  // 函数体
}

// 合并所有reducers
const allReducers = combineReducers({
  Count: CountReducer,
  Person: PersonReducer
})

// ----------------------------------

const store = createStore(
  allReducers,
  composeWithDevTools(applyMiddleware(thunk))
)

export {
  store,
  creatIncrementAction,
  creatDecrementAction,
  creatIncrementAsyncAction
}

App.jsx

下面是 使用 Provider 自动传递 store 给所有的组件

jsx
// App.jsx
import React, { PureComponent } from 'react'
import store from './redux/store'
import { Provider } from 'react-redux'
import { CountContainer } from './containers/Count'
import { PersonContainer } from './containers/Person'
export default class App extends PureComponent {
  render() {
    return (
      <div>
        <Provider store={store}>
          <CountContainer />
          <hr />
          <PersonContainer />
        </Provider>
      </div>
    )
  }
}

UI 组件

下面是将 UI 组件和内容组件进行联系

jsx
// 此处没有引入store,是因为在App.jsx中使用 Provide 自动传递个所有需要调用的组件
import React, { createRef, PureComponent } from 'react'

import {
  creatIncrementAction,
  creatDecrementAction
} from '../../redux/actions/Count'

import { connect } from 'react-redux'

class CountUI extends PureComponent {
  state = {
    count: 0
  }

  selectRef = createRef()

  increment = () => {
    const { value } = this.selectRef.current
    this.props.increment(parseInt(value))
  }
  decrement = () => {
    const { value } = this.selectRef.current
    this.props.decrement(parseInt(value))
  }
  incrementIfOdd = () => {
    const { value } = this.selectRef.current
    if (this.props.count % 2 !== 0) {
      this.props.increment(parseInt(value))
    }
  }
  incrementAsync = () => {
    const { value } = this.selectRef.current
    setTimeout(() => {
      this.props.increment(parseInt(value))
    }, 500)
  }

  render() {
    return (
      <div>
        <h2>我是Count组件</h2>
        <h3>Person组件的总人数为{this.props.persons.length}</h3>
        <h4>{this.props.count}</h4>
        <select ref={this.selectRef}>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>
        <button onClick={this.increment}>增加</button>
        <button onClick={this.decrement}>减少</button>
        <button onClick={this.incrementIfOdd}>奇数增加</button>
        <button onClick={this.incrementAsync}>异步增加</button>
      </div>
    )
  }
}

const mapStateToProps = (state) => ({
  count: state.Count.count,
  persons: state.Person.persons
})

const mapDispatchToProps = {
  increment: creatIncrementAction,
  decrement: creatDecrementAction
}

const CountContainer = connect(mapStateToProps, mapDispatchToProps)(CountUI)

export { CountContainer }

重写 UI 组件

由于有了 hooks 可以不在使用 mapStateToProps 和 mapDispatchToProps 方法,以及 connect 方法使 UI 组件和容器组件进行连接,可以直接在 函数式组件 中使用 hooks 获取 redux 中的数据和操作数据的方法。例如将上面的方法进行改写

jsx
import React, { createRef, memo, useRef } from 'react'

import {
  creatIncrementAction,
  creatDecrementAction
} from '../../redux/actions/Count'

import { useSelector, useDispatch, shallowEqual } from 'react-redux'

export default memo(function Count() {
  const selectRef = useRef()

  const dispatch = useDispatch()
  // 将原本的方法使用dispath()包裹后进行调用即可,不用再重新定义 mapDispatchToProps 对应的方法
  // 相当于原本的 mapStateToProps 中定义的变量
  const { count, persons } = useSelector((state) => {
    return {
      count: state.Count.count,
      persons: state.Person.persons
    }
    // 与原本使用 connect 时只会进行浅层比较不同,默认情况下,使用 useSelector 会对里面的数据进行 深层比较,只要数据发生变化,就会重新加载页面,为了优化性能,一般情况下都需要传递第二个参数 shallowEqual ,使得 useSelector 只会进行浅层比较。
  }, shallowEqual)

  const increment = () => {
    const { value } = selectRef.current
    dispatch(creatIncrementAction(parseInt(value)))
  }

  const decrement = () => {
    const { value } = selectRef.current
    dispatch(creatDecrementAction(parseInt(value)))
  }
  const incrementIfOdd = () => {
    const { value } = selectRef.current
    if (this.props.count % 2 !== 0) {
      dispatch(creatIncrementAction(parseInt(value)))
    }
  }
  const incrementAsync = () => {
    const { value } = selectRef.current
    setTimeout(() => {
      dispatch(creatIncrementAction(parseInt(value)))
    }, 500)
  }

  return (
    <div>
      <h2>我是Count组件</h2>
      <h3>Person组件的总人数为{persons.length}</h3>
      <h4>{count}</h4>
      <select ref={selectRef}>
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3">3</option>
      </select>
      <button onClick={increment}>增加</button>
      <button onClick={decrement}>减少</button>
      <button onClick={incrementIfOdd}>奇数增加</button>
      <button onClick={incrementAsync}>异步增加</button>
    </div>
  )
})

使用 Immutable.js 操作 redux 中的数据(reducer),优化性能

  1. 主要是修改 reducer 中的默认数据,后续修改数据时,使用 immutable 提供的 API 即可。 Immutable 能优化性能的原因,最大化复用原本的数据。一定程度上解决 reducer 中默认数据很多的情况,只修改一个地方,就会将所有数据进行重新修改,重新渲染所有使用了数据的页面。
  • 安装 yarn add immutable

  • 修改设置数据的方法

    jsx
    // 引入 immutable
    import { Map } from 'immutable'
    
    const defaultState = Map({
      topBanners: []
    })
    • 修改 reducer 中设置数据的方法

      jsx
      case 'xxxx':
        return preState.set('topBanners', data)
      default:
        return preState
    • 修改组件中使用了状态时,获取数据的方法

    jsx
    // 获取 store 中存储的数据,
    const { topBanners } = useSelector(
      (state) => ({
        topBanners: state.discover.get('topBanners')
      }),
      shallowEqual
    )
  1. 而合并全部 reducer,则不能直接使用 immutable ,需要使用 redux-immutable ,将原本的 combineReducers 的引入替换,redux-immutable 内部会自动帮助我们进行性能的优化。

    jsx
    - import { combineReducers } from 'redux'
    + import { combineReducers } from 'redux-immutable'
    
    
     // 容器组件
     const { topBanners } = useSelector(
     (state) => ({
    
     - topBanners: state.discover.get('topBanners')
    
     + topBanners: state.get('discover').get('topBanners')
       // 还可以简写,意思是先获取 discover ,再获取 topBanners
       // topBanners: state.getIn(['discover', 'topBanners'])
       }),
       shallowEqual
       )

Pubsub-js 的使用

  1. 安装 yarn add pubsub-js

  2. 订阅

jsx
在生命周期挂载完成后,进行订阅消息
componentDidMount() {
  this.token = PubSub.subscribe('test', (msg, data) => {
    console.log('List 组件收到数据', data)
    this.setState(data)
  })
}

// 取消订阅
componentWillUnmount() {
  PubSub.unsubscribe(this.token)
}
  1. 发布
jsx
PubSub.publish('test', { isFirst: false, isLoading: true })