Re-learning React's state management analysis (redux)

We know that when our project grows, we need to rely on a lot of data, and the data will be used in various components, so we need to share this data to update the page responsively. Chinese document please see here

Redux core content

action

  • Redux requires us to update data through actions. All data changes must be updated through the dispatch action.
  • action is a plain JavaScript object that describes the type and value of this update.
    import {INCREMENT, DECREMENT} from './constants'
    // 返回一个对象{type, value}

    export const increment = num => ({
      type: INCREMENT,
      value: num
    })
    export const decrement = num => ({
      type: DECREMENT,
      value: num
    })
复制代码

reducer

  • A reducer is a pure function. Link state and action together.
  • What the reducer does is combine the incoming state and action to generate a new state.
    import {INCREMENT, DECREMENT} from './constants'
    export default function reducer(state = {count: 0}, action) {
      switch (action.type) {
        case INCREMENT:
          return {...state, count: state.count + action.value}
        case DECREMENT:
          return {...state, count: state.count - action.value}
        default:
          return state
      }
    }
复制代码

store

  • Provide variables and methods that can be accessed.
    import {createStore} from 'redux';
    import reducer from './reducer'

    const store = createStore(reducer);

    export default store;
复制代码

Three Principles of Redux

single source of truth

  • The state of the entire application is stored in an object tree, and this object tree is only stored in one store.
  • Redux does not enforce that we cannot create multiple stores, but doing so is not conducive to data maintenance.
  • A single source of data makes it easy to maintain, track, and modify the state of the entire application.

State is read-only

  • The only way to modify the state must be to trigger an action, do not try to modify the state in any way elsewhere.
  • This ensures that neither View nor network requests can directly modify the state, they can only describe how they want to modify the state through actions.
  • This ensures that all modifications are centralized and executed in strict order, so there is no need to worry about race conditions.

Use pure functions to perform modifications

  • The old state and actions are linked together by the reducer, and a new state is returned.
  • As the complexity of the application increases, we can split the reducer into multiple smaller reducers that operate on different parts of the state tree.
  • But all reducers should be pure functions without any side effects.

The following is a picture provided by the redux official website.image.png

base case

下面我们来看一下在项目中如何使用吧。这个案例就是点击按钮,调整数字,然后响应在两个组件中。

文件结构

image.png

代码

  • actionCreators.js: 定义action对象。写成函数,方便扩展。
    import {INCREMENT, DECREMENT} from './constants'
    // 返回一个对象{type, value}

    export const increment = num => ({
      type: INCREMENT,
      value: num
    })
    export const decrement = num => ({
      type: DECREMENT,
      value: num
    })
复制代码
  • constants.js: 定义action常量,防止多处使用写错。
    export const INCREMENT = "increment"
    export const DECREMENT = "decrement"
复制代码
  • index.js
    import {createStore} from 'redux';
    import reducer from './reducer'

    const store = createStore(reducer);

    export default store;
复制代码
  • reducer.js
    import {INCREMENT, DECREMENT} from './constants'
    export default function reducer(state = {count: 0}, action) {
      switch (action.type) {
        case INCREMENT:
          return {...state, count: state.count + action.value}
        case DECREMENT:
          return {...state, count: state.count - action.value}
        default:
          return state
      }
    }
复制代码
  • 组件一
import React, { PureComponent } from 'react'
import { increment, decrement } from '../store/actionCreators.js'
import store from '../store'
export default class About extends PureComponent {
  constructor(props) {
    super(props)
    this.state = store.getState()
  }

  componentDidMount() {
    store.subscribe(() => {
      this.setState(store.getState())
    })
  }

  decrement(num) {
    store.dispatch(decrement(num))
  }
  increment(num) {
    store.dispatch(increment(num))
  }

  render() {
    return (
      <div>
        <hr />
        <div>about</div>
        <div>{this.state.count}</div>
        <button onClick={(e) => this.decrement(5)}>-5</button>
        <button onClick={(e) => this.increment(5)}>+5</button>
      </div>
    )
  }
}
复制代码
  • 组件二
import React, { PureComponent } from 'react'
import { increment, decrement } from '../store/actionCreators.js'
import store from '../store'
export default class Home extends PureComponent {
  constructor(props) {
    super(props)
    this.state = store.getState()
  }

  componentDidMount() {
    store.subscribe(() => {
      this.setState(store.getState())
    })
  }

  decrement(num) {
    store.dispatch(decrement(num))
  }
  increment(num) {
    store.dispatch(increment(num))
  }

  render() {
    return (
      <div>
        <div>home</div>
        <div>{this.state.count}</div>
        <button onClick={(e) => this.decrement(5)}>-5</button>
        <button onClick={(e) => this.increment(5)}>+5</button>
      </div>
    )
  }
}
复制代码

btn2.gif

封装高阶组件

从上面的代码可以看出,有很多重复的代码逻辑,所以我么可以抽离代码,让其变的更简洁。

  • mapStateToProps。高阶组件中,调用该函数并传入state。

其实我们的逻辑还是在组件中书写的,即mapStateToProps的方法体。

  • mapDispatchToProps。高阶组件中,调用该函数并传入store.dispatch。

其实我们的action函数依旧是在组件中写的,只是我们在高阶组件中触发而已。

    import React, { PureComponent } from 'react'
    import store from '../store'

    export default function StoreHOC(mapStateToProps, mapDispatchToProps) {
      return (WrappedComponent) => {
        class HoComponent extends PureComponent {
          constructor(props) {
            super(props)
            this.state = store.getState()
          }

          componentDidMount() {
            store.subscribe(() => {
              this.setState(store.getState())
            })
          }
          render() {
            return (
              <WrappedComponent
                {...this.props}
                {...mapStateToProps(this.state)}
                {...mapDispatchToProps(store.dispatch)}
              />
            )
          }
        }
        return HoComponent
      }
    }
复制代码

使用

    import React, { PureComponent } from 'react'
    import { increment, decrement } from '../store/actionCreators.js'

    import StoreHOC from './StoreHOC'
    class Home extends PureComponent {


      render() {
        return (
          <div>
            <div>home</div>
            <div>{this.props.count}</div>
            <button onClick={(e) => this.props.decrement(5)}>-5</button>
            <button onClick={(e) => this.props.increment(5)}>+5</button>
          </div>
        )
      }
    }

    const mapStateToProps = (state) => ({
      ...state
    })
    const mapDispatchToProps = (dispatch) => ({
      decrement(num) {
        dispatch(decrement(num))
      },
      increment(num) {
        dispatch(increment(num))
      }
    })

    export default StoreHOC(mapStateToProps, mapDispatchToProps)(Home)
复制代码

再次优化

但是上面代码的耦合性还是挺高的,必须的引入store。如果你想让别人使用这个高阶组件,那么他们也需要在你的代码中导入store。所以就有了下面的封装。

我们可以通过React.createContext。

    // context.js
    import  {createContext} from 'react';

    const StoreContext = createContext();
    export default StoreContext
复制代码

高阶组件

  • 就是将store换成this.context
import React, { PureComponent } from 'react'
- // import store from '../store'
import StoreContext from './context.js'

export default function StoreHOC(mapStateToProps, mapDispatchToProps) {
  return (WrappedComponent) => {
    class HoComponent extends PureComponent {
      + static contextType = StoreContext
      + constructor(props, context) {
        super(props, context)
        this.state = context.getState()
      }

      componentDidMount() {
       - // 需要跟新state
       - // store.subscribe(() => {
       - //   this.setState(store.getState())
       - // })
       + this.context.subscribe(() => {
       +   this.setState(this.context.getState())
       + })
      }
      render() {
        return (
          <WrappedComponent
            {...this.props}
            {...mapStateToProps(this.state)}
            + {...mapDispatchToProps(this.context.dispatch)}
          />
        )
      }
    }
    return HoComponent
  }
}
复制代码

使用


    import StoreContext from './storeTest/context'
    import store from './store'
    ReactDOM.render((
        <div>
          <StoreContext.Provider value={store}>
            <Home></Home>
            <About></About>
          </StoreContext.Provider>
        </div>
    ), document.getElementById("root"))
复制代码

使用react-redux

但是在工作中,我们都会使用react-redux库。

安装

    npm install react-redux
复制代码

使用

使用方法和上面我们自己封装的高阶函数一样。只不过我们给Provider组件设置共享数据时,是使用store,而非value。

    import React from 'react'
    import ReactDOM from 'react-dom'
    import './index.css'
    import App from './App'
    import store from './app/store'
    import { Provider } from 'react-redux'

    ReactDOM.render(
      <Provider store={store}>
        <App />
      </Provider>,
      document.getElementById('root')
    )
复制代码

redux处理异步

我们只打,当我们做项目时,需要请求数据,所以就需要发送网络请求获取数据。那么如何在redux中处理异步请求呢?

网络请求位置

  • 可以放在组件的生命周期函数中。
  • 可以放在redux中。

但是网络请求到的数据也属于我们状态管理的一部分,更好的应该是将其也交给redux来管理。所以我们就需要使用redux-thunk来完成这些操作。

redux-thunk的介绍

  • 默认情况下的dispatch(action),action需要是一个JavaScript的对象。
  • redux-thunk can make dispatch (action function), action can be a function.
    • This function will be called automatically, and will be passed a dispatch function and getState function to this function.
    • The dispatch function is used for us to dispatch the action again later.
    • The getState function takes into account that some of our subsequent operations need to rely on the original state, so that we can obtain some previous states.

specific use

We need applyMiddleware built into redux to use redux-thunk.

    import {createStore, applyMiddleware, compose} from 'redux'

    import reducer from './reducer'

    import thunk from 'redux-thunk'
    
    // 这里也使用了redux-devtools工具
    const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({trace: true}) : compose

    const enhancer = composeEnhancers(applyMiddleware(thunk))
    const store = createStore(
      reducer,
      enhancer
    )

    export default store
复制代码

Send network requests in action operations.

    export const getBannerList = (dispatch, getState) => {
      axios({
        url: 'http://123.207.32.32:8000/home/multidata'
      }).then((res) => {
      // 二次dispatch
        dispatch({
            type: "getBannerList",
            value: res.data.data.banner.list
          })
      })
    }
复制代码

some logic in the component

    componentDidMount() {
    // 调用映射后的action函数。实现一次dispatch。
        this.props.getBannerList()
    }
    const mapDispatchToProps = (dispatch) => ({
      getBannerList() {
        dispatch(getBannerList)
      }
    })
    export default connect(mapStateToProps, mapDispatchToProps)(Home)
复制代码

Simple analysis of redux middleware

    function thunk(store) {
      const next = store.dispatch;
      // 把它当做dispatch函数
      function dispatchAndThunk(action) {
        if(typeof action === "function") {
          action(store.dispatch, store.getState)
        }else {
          next(action)
        }
      }
      // 方式一
      // store.dispatch = dispatchAndThunk
      // 方式二
      return dispatchAndThunk
    }


    function applyMiddleware(...middleware) {
      for(let i = 0; i < middleware.length; i++) {
      // 方式一
      // middleware[i](store)
      // 方式二
        store.dispatch = middleware[i](store)
      }
    }
    // 可以传入多个中间件函数
    applyMiddleware(thunk)
复制代码

split reducer

We found that as the project grows and the amount of data increases, if all the states are managed in a reducer, the reducer will also become very bloated. Bad for maintenance. So we need to split the reducer.

How to split it?

Students who have studied vuex know that there is a concept of modules. So we can also divide the corresponding data into a module, and let each maintain its own reducer, actionCreator, constants files. Then merge in the total reducer. image.pngSo how to merge in the total reducer?

At this time, we need to use the combineReducers function provided by redux.image.png

This part of redux is still relatively difficult to understand, and it will not be used out of the box like vuex. So you still need to understand redux well and practice a lot.

Guess you like

Origin juejin.im/post/7079277020521185293