Hooks in the React comprehensive transformation, will find that the React Hooks also can realize the flux data flow simple logic, let a person can not help some excitement, then went to React – Redux of the official document, so this affair last year has already been arranged, React-redux: react-redux: react-redux: react-Redux: react-Redux: react-Redux: react-Redux: react-Redux: react-Redux

Support is available in V7.1.0hooks, currently v7.2.1

The React Redux: github.com/reduxjs/rea…

Hooks Lower version 🚲

The key to implementing react-Redux is Context. Context implements data sharing between components. By wrapping the top component with a Provider and passing in a value value, sub-components at all levels can obtain the implementation-defined common state through Context objects.

The class component calls the context using this.context, and the functional component does not have this pointing to useContext, which is used in the Hooks API to retrieve the context. The dispatch method provided by useReducer will be familiar to anyone who has used Redux.

Hooks API: zh-hans.reactjs.org/docs/hooks-…

import React, { createContext, useContext, useReducer } from 'react'

export interface User {
  name: string.phone: string,}interface StateType {
  searchWords: string.userList: User[]
}

type ActionType = {
  type: 'UPDATE_SEARCH'.payload: string
} | {
  type: 'UPDATE_LIST'.payload: User[]
}

// Define a Reducer distribution rule
const reducer = (state: StateType, action: ActionType): StateType= > {
  switch (action.type) {
    case 'UPDATE_SEARCH':
      return {
        ...state,
        searchWords: action.payload
      };
    case 'UPDATE_LIST':
      return {
        ...state,
        userList: action.payload
      };
    default:
      break;
  }
  return state
}

// create initialization state, dispatch
const initState: StateType = {
  searchWords: ' '.userList: []}const initDispatch = (action: ActionType): void= > {}

// Create context context
const StoreCtx = createContext(initState);
const DispatchCtx = createContext(initDispatch);

const ReduxHooks: React.FC<{ children: JSX.Element }> = ({ children }) = > {
  Create a reducer and inject global state and dispatch
  const [state, dispatch] = useReducer(reducer, initState);

  return (
    <DispatchCtx.Provider value={dispatch}>
      <StoreCtx.Provider value={state}>
        {children}
      </StoreCtx.Provider>
    </DispatchCtx.Provider>
  );
};

type SelectType = StateType | User[] | string

export const useSelector = (selector: (params: StateType) = > SelectType): SelectType= > {
  const store = useContext(StoreCtx)
  return selector(store)
}

export const useDispatch = () = > {
  const dispatch = useContext(DispatchCtx)
  return dispatch
}

export default ReduxHooks

Copy the code

By binding useReducer’s state and dispatch to the Context, inject it into all child components wrapped by the ReduxHooks component. Then provide two custom Hooks that enable the child to retrieve store and Dispatch from the functional component, treating store and Dispatch as dynamic data within the context.

// The top-level parent component
const Parrent = () = > {
  return (
    <ReduxHooks>
      <>
        <UserSearch />
        <UserList />
      </>
    </ReduxHooks>
  )
}

const Child1 = () = > {
  const searchWords = useSelector(state= > state.searchWords) as string
  const userList = useSelector(state= > state.userList) as User[]
  
  return (
    <div>{searchWords}</div>)}const Child2 = ()  => {
  const searchWords = useSelector(state= > state.searchWords) as string
  const dispatch = useDispatch()

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) = > {
    dispatch({
      type: 'UPDATE_SEARCH'.payload: e.target.value
    })
  }

  return (
    <div>
      <input type="text" value={searchWords} onChange={handleChange} />
    </div>)}Copy the code

The parent component uses ReduxHooks as the root node. The child component calls useSelector and useDispatch. UseSelector passes in a pure function to get the values in the store. This avoids dirty data caused by direct store operations.

However, this is only a low-profile version of Redux, which only realizes the basic operation of flux data flow without better optimization, and has the following problems:

  • storeAny changes to the data in the file will cause the child component with reference data to update and become unusablememoTo optimize the
  • Can’t useReduxMiddleware,redux-saga 等
  • Is not conducive toRedux DevToolsdebugging

PS: If you’re struggling with TypeScript code, take a look at an article I wrote earlier:

The TypeScript how to write the React Hooks | nuggets technical essay – double festival special article ✨

The React – Redux 🚀

The react-Redux library does some of the dirty work. The react-Redux API is also powerful enough. Let’s see how react-Redux is used.

Basic usage

import { Provider } from 'react-redux'
import { createStore } from 'redux'

Reducer and initState follow the Hooks version above
const store = createStore(reducer, initState)

const ReduxProvider: React.FC<{ children: JSX.Element }> = ({ children }) = > {
  return (
    <Provider store={store}>
      {children}
    </Provider>)}Copy the code

Here we implement the ReduxProvider root component with the official Redux combo and use it in the same way as the previous low release. We use createStore to produce states and dispatches. The createContext and useReducer providers are provided by React-redux and do not require the developer to bind the context.

import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { StateType } from '@/utils/ReduxProvider'

const Child2: React.FC = ()  => {
  const searchWords = useSelector((state: StateType) = > state.searchWords)
  const dispatch = useDispatch()

  return (
    <div
      onClick={()= > {
        dispatch({
          type: 'UPDATE_SEARCH',
          payload: 'another'
        })
      }}
    >
      {searchWords}
    </div>)}Copy the code

If we use useSelector, we pass in a pure function. The parameter that the pure function receives is Redux’s Store.

useSelector

Redux has performance issues because every time the value of the Provider changes, the useSelector hooks injected by the child components also fire, causing the child components to rerender, so to perfect the need for redundant reflusher, We still have to start with useSelector. Coincidentally, useSelector provides an equalityFn to help with optimization.

Note in the source code: @param {Function} selector the selector function @param {Function=} equalityFn the function that will be used to determine equality

Source code portal -useSelector

SeletedState is cached internally by useSelector. EqualityFn is used to compare the current seletedState with the last seletedState executed. UseSeletore then triggers a component refresh and does nothing if the condition is met. By default, if equalityFn is not specified, react-redux uses congruence === for strict comparison.

const [, forceRender] = useReducer(s= > s + 1.0) // Call forceRender to refresh the component

const refEquality = (a, b) = > a === b // Default equal function
Copy the code

However, for complex reference data types, the default strict comparison === is not enough. React-redux provides shallowEqual functions for shallow comparison of objects, arrays, and other data types. The official documentation also states that Lodash’s comparison functions _.isequal () and Immutable. Js are suitable for this scenario.

While we’re talking about redux’s performance optimizations, there’s another great wheel to mention: Reselect. Three features of ResELECT:

  • letReduxStore state data of the smallest granularity and combine derived live commandsreselectTo dry
  • reselectcachesselectorRecalculation is not performed until dependent parameters change
  • You can take the othersselectorUse in combination

There are a lot of benefits, but a lot of digging friends can be ambiguous. Name onereduxYou can understand the simple functions inreselectorNow,connectthisHOCI think we all know how to make friends,connectBy way of higher-order componentsredux 中 storeThe data is bound to the child component’spropsTo allow the child component to passpropsYou can getreduxPublic status data. And callconnectThe first parameter that we receive ismapStateToProps ,mapStateToPropsSo this pure function is actually going to beselector 。

Reselect ensures that selectors that rely on the Redux data stream will be cached to the maximum extent possible. Caching is meant to reduce the execution of complex logic, thereby ensuring performance.

import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'

const lenOfSearch = createSelector(
  (state: StateType) = > state.searchWords,
  words= > words.length
)

const Child1: React.FC = ()  => {
  const len = useSelector(lenOfSearch)

  return (
    <div>
      {len}
    </div>)}Copy the code

If searchWords is not changed, len in Child1 will not be updated, and Child1 will not be updated. Somewhat like useMemo and VUE, computed data is used to cache complex computations, but there are other more advanced uses that we won’t expand on here, and you can refer to the official repository.

Reselect: github.com/reduxjs/res…

Provider

Another core API is the React-Redux Provider. The core concept of the React-Redux Provider is the createContext of React.

export const ReactReduxContext = /*#__PURE__*/ React.createContext(null)
Copy the code

However, react-Redux also has some performance improvements, additional subscriptions, the ability to customize context, and basically fully embraces the Hooks API.

Source code portal –Provider

function Provider({ store, context, children }) {
  // Cache contextValue, update subscription when store changes
  const contextValue = useMemo(() = > {
    const subscription = new Subscription(store)
    subscription.onStateChange = subscription.notifyNestedSubs
    return {
      store,
      subscription
    }
  }, [store])

  const previousState = useMemo(() = > store.getState(), [store])

  useEffect(() = > {
    const { subscription } = contextValue
    subscription.trySubscribe()

    // Data changes trigger subscription notification
    if(previousState ! == store.getState()) { subscription.notifyNestedSubs() }return () = > {
      subscription.tryUnsubscribe()
      subscription.onStateChange = null
    }
  }, [contextValue, previousState])

  // You can also customize the context
  const Context = context || ReactReduxContext

  return <Context.Provider value={contextValue}>{children}</Context.Provider>
}
Copy the code

Custom context works a bit like the lower version of the Hooks mentioned above. The following code rewrites ReactHooks to implement local public state management, as opposed to the globally provided useSelector.

import {
  Provider,
  createSelectorHook
} from 'react-redux'

const StoreCtx = React.createContext(null);
const myStore = createStore(reducer);

// Export useSelector hooks bound to custom context
export const useCustomSelector = createSelectorHook(StoreCtx);

const ReduxHooks: React.FC<{ children: JSX.Element }> = ({ children }) = > {
  return (
    <Provider context={StoreCtx} store={myStore}>
      {children}
    </Provider>
  );
};

export default ReduxHooks
Copy the code

Dva upgrade

The original studyreduxWhile reading ali’sdva.js , dva.jsGreatly simplifiedreduxConfigures functionality that allows developers to only care about business logic, now though the official maintenance is not muchdva.jsAnd focus on the new oneumi.jsFrameworks have also been added to the plug-in collectiondva.jsPlugins are also supported in the documentationhooksBut needs to be upgraded toX 2.6.To just go.If it’s useful in the projectdvaOr,umiIf so, take a look at the version.

For more information on how model is defined, see the documentation. Here is a brief demonstration of how the DVA plug-in provides useSelector.

import React from 'react';
import { useSelector } from 'umi'
import { IndexModelState } from '@/models/index'

export default() = > {const name = useSelector((state: { index: IndexModelState }) = > state.index.name)

  return (
    <div>
      <h1>Page index</h1>
      <span>{name}</span>
    </div>
  );
}

Copy the code

After opening dVA plug-in configuration, you need to run UMI generate TMP to introduce useSelector. Here is a fun reference to dVA type derivation. The previously defined IndexModelState interface needs to be introduced here to prompt type derivation in subsequent code.

Umi plugin: umijs.org/zh-CN/plugi…

The end of the

Facebook recently released a new state management framework, Recoil, which is still in the experimental stage. After all, it’s an official wheel, and it looks promising. However, Redux is so mature in the React ecosystem that it’s unlikely to be replaced anytime soon. So it is necessary to learn Redux well.

Finally, post the code in the project repository:

github

PS: If there are any mistakes in this article, please kindly correct them

Past wonderful 📌

  • The TypeScript how to write the React Hooks | nuggets technical essay – double festival special article ✨
  • React DevTools ✨
  • Vue3 hugs TypeScript properly ✨
  • Vue3-one piece尝鲜:React Hooks VS Composition API ✨
  • Optimize performance ✨