Volver a aprender el análisis de gestión de estado de React (redux)

Sabemos que cuando nuestro proyecto crece, necesitamos confiar en una gran cantidad de datos, y los datos se utilizarán en varios componentes, por lo que debemos compartir estos datos para actualizar la página de forma receptiva. Documento chino por favor vea aquí

Contenido básico de Redux

acción

  • Redux requiere que actualicemos datos a través de acciones. Todos los cambios de datos deben actualizarse a través de la acción de envío.
  • action es un objeto de JavaScript sin formato que describe el tipo y el valor de esta actualización.
    import {INCREMENT, DECREMENT} from './constants'
    // 返回一个对象{type, value}

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

reductor

  • Un reductor es una función pura. Vincular el estado y la acción juntos.
  • Lo que hace el reductor es combinar el estado entrante y la acción para generar un nuevo estado.
    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
      }
    }
复制代码

Tienda

  • Proporcione variables y métodos a los que se pueda acceder.
    import {createStore} from 'redux';
    import reducer from './reducer'

    const store = createStore(reducer);

    export default store;
复制代码

Tres principios de Redux

única fuente de verdad

  • El estado de toda la aplicación se almacena en un árbol de objetos, y este árbol de objetos solo se almacena en una tienda.
  • Redux no exige que no podamos crear múltiples tiendas, pero hacerlo no es propicio para el mantenimiento de datos.
  • Una única fuente de datos facilita el mantenimiento, el seguimiento y la modificación del estado de toda la aplicación.

El estado es de solo lectura

  • La única forma de modificar el estado debe ser desencadenar una acción, no intente modificar el estado de ninguna manera en ningún otro lugar.
  • Esto asegura que ni las solicitudes de vista ni de red puedan modificar directamente el estado, solo pueden describir cómo quieren modificar el estado a través de acciones.
  • Esto asegura que todas las modificaciones estén centralizadas y ejecutadas en estricto orden, por lo que no hay necesidad de preocuparse por las condiciones de carrera.

Usar funciones puras para realizar modificaciones

  • El reductor une el estado anterior y las acciones, y se devuelve un estado nuevo.
  • A medida que aumenta la complejidad de la aplicación, podemos dividir el reductor en varios reductores más pequeños que operan en diferentes partes del árbol de estado.
  • Pero todos los reductores deben ser funciones puras sin efectos secundarios.

La siguiente es una imagen proporcionada por el sitio web oficial de redux.imagen.png

caso base

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

文件结构

imagen.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 puede hacer envío (función de acción), la acción puede ser una función.
    • Esta función se llamará automáticamente y se le pasará una función de despacho y una función getState a esta función.
    • La función de envío se utiliza para que podamos enviar la acción nuevamente más tarde.
    • La función getState tiene en cuenta que algunas de nuestras operaciones posteriores deben basarse en el estado original, para que podamos obtener algunos estados anteriores.

uso específico

Necesitamos applyMiddleware integrado en redux para usar 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
复制代码

Enviar solicitudes de red en operaciones de acción.

    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
          })
      })
    }
复制代码

algo de lógica en el componente

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

Análisis simple de middleware redux

    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)
复制代码

reductor dividido

Descubrimos que a medida que el proyecto crece y la cantidad de datos aumenta, si todos los estados se administran en un reductor, el reductor también se hinchará mucho. Malo para el mantenimiento. Así que tenemos que dividir el reductor.

¿Cómo dividirlo?

Los estudiantes que han estudiado vuex saben que existe un concepto de módulos. Así que también podemos dividir los datos correspondientes en un módulo, y dejar que cada uno mantenga sus propios archivos de constantes, actionCreator y reductor. Luego fusionar en el reductor total. imagen.pngEntonces, ¿cómo fusionarse en el reductor total?

En este momento, necesitamos usar la función combineReducers proporcionada por redux.imagen.png

Esta parte de redux todavía es relativamente difícil de entender, y no se usará de manera inmediata como vuex. Por lo tanto, aún necesita comprender bien redux y practicar mucho.

Supongo que te gusta

Origin juejin.im/post/7079277020521185293
Recomendado
Clasificación