above

The main function of React-Redux is to link Redux and React. Use the provided connect method to enable any react component to obtain the state of the global Store. This is done by storing a store in the context provided by the provider. When connect is called, the props of the component can be replaced so that it can access the customized data or methods.

The target

This article will try to replace the basic functionality of React-Redux with the recently popular React-Hook.

Let’s first list the desired features to replace react-redux:

  • Maintain a store globally.
  • Any component can be retrieved from the Store, and preferably props can be customized (mapStatetoProps).
  • Provides the ability to dispatch actions (mapDispatchtoProps).

useRudecer

Let’s take a look at what an official instance of the built-in useRudecer tells us:

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'reset':
      return initialState;
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      // A reducer must always return a valid state.
      // Alternatively you can throw an error if an invalid action is dispatched.
      returnstate; }}function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, {count: initialCount});
  return (
    <div>
        Count: {state.count}
        <button onClick={()= > dispatch({type: 'reset'})}>
            Reset
        </button>
        <button onClick={()= > dispatch({type: 'increment'})}>+</button>
        <button onClick={()= > dispatch({type: 'decrement'})}>-</button>
      <div/>
  );
}

Copy the code

At first glance, react uses a hook that uses redux. The state is changed by the action being dispatched, and the data flow is one-way. But hooks do not allow state sharing, which means that each time the useReducer holds data, the data is independent. Take this example:


function CountWrapper() {
    return (
        <section>
            <Counter initialCount={1}/>
            <Counter initialCount={1}/>
        </setion>
        )
}
Copy the code

The data inside the two Count components is independent and cannot affect each other, making state management impossible. The reason is that useReducer is also implemented internally with useState

function useReducer(reducer, initialState) {
  const [state, setState] = useState(initialState);

  function dispatch(action) {
    const nextState = reducer(state, action);
    setState(nextState);
  }

  return [state, dispatch];
}
Copy the code

StoreProvider

UseReducer doesn’t seem to be helping. React-redux provides a Provider that uses context to solve global state problems. Here you can use useContext, the built-in hook.

Accepts a context object (the value returned from React.createContext) and returns the current context value, as given by the nearest context provider for the given context. When the provider updates, this Hook will trigger a rerender with the latest context value.

It accepts a context object returned by react.createcontext, and useContext can return the latest value when the provider updates, which in this article is interpreted as an incoming store update. So we have the following code

import {createContext, useContext} from 'react';

const context = createContext(null);
export const StoreProvider = context.provider;

const store = useContext(context);
// do something about store.

Copy the code

useDispatch

Here we provide a root component to accept the store. When the store gets updated, we can also use useContext to get the latest value. A hook is exposed to return the dispatch on the store to issue an action to change the state

export function useDispatch() {
  const store = useContext(Context);
  return store.dispatch;
}
Copy the code

useStoreState

Next, look at the component fetching data from the Store. To retrieve the global state, write a custom hook call to store.getStore().

export function useStoreState(mapState){
    const store = useContext(context);
    return mapState(store.getStore());
}
Copy the code

This takes the state away, but it leaves out the very important issue of how to tell the component to fetch new data again when the data on the Store changes. When the store changes, it’s not associated with the view. Another problem is not looking at mapState changes. For the first problem, we can use the useEffect built-in hook to complete the subscription on the store when the component is mounted and unsubscribe when the component is unmont. Changes to mapState can be monitored using useState and executed to the corresponding setter method each time a change occurs. The following code

export function useStoreState(mapState) {
    const store = useContext(context);

    const mapStateFn = (a)= > mapState(store.getState());

    const [mappedState, setMappedState] = useState((a)= > mapStateFn());

    // If the store or mapState change, rerun mapState
    const [prevStore, setPrevStore] = useState(store);
    const [prevMapState, setPrevMapState] = useState((a)= > mapState);
    if(prevStore ! == store || prevMapState ! == mapState) { setPrevStore(store); setPrevMapState((a)= > mapState);
        setMappedState(mapStateFn());
    }

    const lastRenderedMappedState = useRef();
    // Set the last mapped state after rendering.
    useEffect((a)= > {
        lastRenderedMappedState.current = mappedState;
    });

    useEffect(
        (a)= > {
            // Run the mapState callback and if the result has changed, make the
            // component re-render with the new state.
            const checkForUpdates = (a)= > {
                const newMappedState = mapStateFn();
                if (!shallowEqual(newMappedState, lastRenderedMappedState.current)) {
                    setMappedState(newMappedState);
                }
            };
                        
            // Pull data from the store on first render.
            checkForUpdates();

            // Subscribe to the store to be notified of subsequent changes.
            const unsubscribe = store.subscribe(checkForUpdates);

            // The return value of useEffect will be called when unmounting, so
            // we use it to unsubscribe from the store.
            return unsubscribe;
        },
        [store, mapState],
    );
    return mappedState
}

Copy the code

As mentioned above, the react-Redux function rewrite of Hook was completed. In terms of the amount of code, it was much simpler and more suitable for the future development direction of React. It can be seen that react-redux will be gradually replaced by hook. This article explains how redux-React-hook is implemented. To try it online, click on the Codesandbox

Follow the official account of IVWEB community to get the latest technology weekly