React Context provides a way to pass data across the component tree without manually adding props to each layer of components. This way, we don’t need to communicate through layers of props, which greatly reduces code complexity and introduces new problems

  1. Components become difficult to reuse
  2. The Provider component provided by the Context object allows the consuming component to subscribe to changes to the Context, and any changes to the consuming component within it will be re-rendered, causing performance problems.

Although we can handle the second point using useMemo:

function Button() {
    let {theme} = useContext(appContext)
    
    return useMemo(() => {
        return <Children class={theme} />
    }, [theme])
}
Copy the code

However, manual optimization and dependency management inevitably bring a certain degree of mental burden. As a well-known state management library in the community, React-Redux must be used by many large projects, where states may be scattered under various modules. How does it solve the above performance defects?

We know thatReduxReact-redux is a single state machine that only cares about state changes. How the view layer changes depends on react-Redux.

Take a look at how we usually use it in code :(full code 👇)

import {createStore} from 'redux'
import reducer from './reducer'
import {Provider} from 'react-redux'

let store = createStore(todoApp);

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

function App() {
  return (
    <>
      <WrapChildFunc />
      <WrapChildClass />
    </>
  )
}
Copy the code

React-redux provides a Provider that receives a store object

function Provider({ store, context, children }) { 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() if (previousState ! == store.getState()) { subscription.notifyNestedSubs() } return () => { subscription.tryUnsubscribe() subscription.onStateChange = null } }, [contextValue, previousState]) const Context = context || ReactReduxContext return <Context.Provider value={contextValue}> {children}</ context. Provider> // Render child elements to make the entire application a child component of the Provider}Copy the code

Providers do two main things:

  1. Wrap a layer on the original application components to make the entire application a child component of the Provider
  2. The store of the Redux is received as props, passed through the context object to the contained consumer component

Again, use the Context object to share data with other components

The Class components

class ChildClass extends React.Component { constructor(props) { super(props) console.log('class', this.props, this.props.children) } render() { const {ClassNum, addClick, ReduceClick} = this.props return (<> <div className="App">{ClassNum}</div> <div>Class component </div> <button onClick={addClick}>+</button> <button onClick={reduceClick}>-</button> </> ) } } const mapStateToProps = state => { return { ClassNum: state.ClassNum } } const mapDispatchToProps = dispatch => { return { addClick: () => { dispatch({type: 'CLASS_ADD'}) }, reduceClick: () => { dispatch({type: Export const WrapChildClass = connect(mapStateToProps, mapDispatchToProps)(ChildClass)Copy the code

The CONNECT module is a high-level component that:

  1. Connect uses context to get the store in the Provider and store.getState() to get the state tree
  2. The connect module returns the function wrapWithComponent
  3. WrapWithConnect returns a ReactComponent object, Connect, that rerenders the original WrappedComponent (UI component) passed in, The mapStateToProps and mapDispatchToProps passed in connect are merged with the props on the component and passed to the WrappedComponent as properties

The specific code is more complex, you can take a look at the source code

The Function components

Let’s focus on the Hooks method

Export default function Child () {const {FuncNum} = useSelector((state: stateType) => { console.log('Func', state) return { FuncNum: FuncNum}}) const dispth = useDispatch() const addClick = useCallback(() => dispth({type: 'FUNC_ADD'}), [FuncNum], ) const reduceClick = useCallback( () => dispth({type: 'FUNC_REDUCE'}), [FuncNum], Return useMemo(() => (<div> <div className="App">{FuncNum}</div> {math.random ()} <div>Func component </div> <button onClick={addClick}>+</button> <button onClick={reduceClick}>-</button> </div> ), [FuncNum]) }Copy the code

As can be seen, we obtain the corresponding data of the state object in store through useSelector, and then obtain the reference of Dispatch through useDispatch method

useDispatch
export function createDispatchHook(context = ReactReduxContext) {
  const useStore =
    context === ReactReduxContext ? useDefaultStore : createStoreHook(context)

  return function useDispatch() {
    const store = useStore()
    return store.dispatch
  }
}
Copy the code

When we dispatch(Action), the state changes. How does React know we need to update the view? The key is useSelector

useSelector
The function useSelectorWithStoreAndSubscription (selector, equalityFn, store, contextSub) {/ / force rendering reducer: S => s+1, state initial value: 0; forceRender ===> dispatch const [, forceRender] = useReducer(s => s + 1, Const subscription = useMemo(() => new subscription (store, contextSub), [store, contextSub) ContextSub]) const latestSubscriptionCallbackError = useRef () / / select function const latestSelector = useRef () / / State const latestSelectedState = useRef() // Select returns state const latestSelectedState = useRef() const storeState = store.getState() let selectedState try { if ( selector ! == latestSelector.current || storeState ! = = latestStoreState. Current | | latestSubscriptionCallbackError. Current) {/ / useSelector select state selectedState = selector(storeState) } else { selectedState = latestSelectedState.current } } catch (err) { ... } // effect hook useIsomorphicLayoutEffect(() => { latestSelector.current = selector latestStoreState.current = storeState latestSelectedState.current = selectedState latestSubscriptionCallbackError.current = undefined }) // effect hook useIsomorphicLayoutEffect(() => { function checkForUpdates() { try { const newSelectedState = LatestSelector. Current (store.getState())) if (equalityFn(newSelectedState, latestSelectedState.current)) { return } latestSelectedState.current = newSelectedState } catch (err) { LatestSubscriptionCallbackError. Current = err} forceRender ()} / / stateChange callback subscription. The onStateChange = checkForUpdates subscription.trySubscribe() checkForUpdates() return () => subscription.tryUnsubscribe() }, [store, subscription]) return selectedState } export function createSelectorHook(context = ReactReduxContext) { const useReduxContext = context === ReactReduxContext ? useDefaultReduxContext : () => useContext(context) return function useSelector(selector, equalityFn = refEquality) { const { store, subscription: contextSub } = useReduxContext() const selectedState = useSelectorWithStoreAndSubscription( selector, equalityFn, store, contextSub ) return selectedState } }Copy the code
  • Const [, forceRender] = useReducer(s => s + 1, 0) const [, forceRender] = useReducer(s => s + 1, 0

    CheckForUpdates: Processing logic triggered by the subscription function after the store changes

  • Key process: Initialization

    1. Use the selector passed in by useSelector to get the corresponding value of the store in Redux
    2. Defines a latestSelectedState that holds the value returned by the last selector
    3. Define the state change handler function checkForUpdates
    4. Subscribe to redux’s store once. After the next store change, the subscription function is triggered to perform checkForUpdates
  • Key process: Update

    1. When the user dispacth triggers a store change, the subscription function performs checkForUpdates
    2. Get the latest state value with store.getState(), compare newSelectedState with latestSelectedState with equalityFn, and execute forceRender if there is any change. React creates an update object and forces rendering. Otherwise just return