Перепишите react-redux с помощью react-hook

React.js

Преамбула

Основная функция, предоставляемая react-redux, заключается в том, чтобы связать redux и реакцию. Использование предоставленного метода подключения позволяет любому компоненту реакции получать состояние глобального хранилища. Метод реализации заключается в сохранении хранилища в контексте, предоставленном провайдером.При вызове подключения реквизиты компонента могут быть заменены, чтобы он мог получить доступ к настроенным данным или методам.

Цель

В этой статье мы попытаемся использовать популярный в последнее время react-hook для замены основных функций react-redux.

Давайте сначала перечислим идеальные функции и завершим их в качестве замены react-redux:

  • Магазин поддерживается глобально.
  • Магазин может получить любой компонент, желательно пропсы можно настроить (mapStatetoProps).
  • Предоставляет возможность отправки действий (mapDispatchtoProps).

useRudecer

Давайте посмотрим, что нам может дать официальный пример встроенного использования Rudecer:

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.
      return state;
  }
}

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/>
  );
}

На первый взгляд кажется, что в React уже можно использовать механизм редукции с помощью хуков, состояние меняется по отправленному действию, а поток данных односторонний. Но хуки не имеют общего состояния, то есть данные, поддерживаемые каждым useReducer, независимы. Например следующий пример:


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

Данные внутри двух компонентов Count независимы и не могут влиять друг на друга, поэтому говорить об управлении состоянием не приходится. Причина в том, что useReducer также реализован внутри с помощью useState.

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

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

  return [state, dispatch];
}

StoreProvider

useReducer не помогает. Чтобы решить проблему глобального состояния, вы можете обратиться к практике react-redux, предоставить Provider и использовать для этого метод контекста. Здесь вы можете использовать встроенный хук useContext.

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.

Он принимает объект контекста, возвращаемый React.createContext, При обновлении провайдера, под которым здесь понимается входящее обновление хранилища, useContext может вернуть самое последнее значение. Тогда у нас есть следующий код

import {createContext, useContext} from 'react';

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

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

useDispatch

На данный момент мы предоставили корневой компонент для приема хранилища. Когда хранилище обновляется, мы также можем использовать useContext для получения последнего значения. В это время открывается хук, чтобы вернуть отправку в хранилище, чтобы отправить действия для изменения состояния.

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

useStoreState

Далее сосредоточимся на проблеме получения компонентами данных в хранилище. На самом деле это очень просто, у всех нас есть хранилище, напишите собственный хук для вызова store.getStore(), чтобы получить глобальное состояние,

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

Хотя здесь и получается состояние, игнорируется очень важный вопрос, как уведомить компонент, чтобы он снова получил новые данные при изменении данных в хранилище. Когда магазин изменяется, он не связан с представлением. Другая проблема заключается в том, что вы не обращаете внимания на изменения mapState. Для решения первой проблемы мы можем использовать встроенный хук useEffect, чтобы завершить подписку на хранилище, когда компонент смонтирован, и отменить подписку, когда он не смонтирован. Изменения в mapState можно отслеживать с помощью useState, и соответствующий метод установки выполняется каждый раз, когда происходит изменение. код показывает, как показано ниже

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

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

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

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

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

    useEffect(
        () => {
            // Run the mapState callback and if the result has changed, make the
            // component re-render with the new state.
            const checkForUpdates = () => {
                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
}

Как и выше, переписывание функции react-redux by hook завершено, что довольно лаконично с точки зрения размера кода, а метод реализации больше соответствует будущему направлению развития реакции. Видно, что с большой вероятностью react-redux будет постепенно заменяться на hook-метод. Эта статья правильнаяredux-react-hookОбъясняется принцип реализации, если вы хотите попробовать содержание этой статьи онлайн, нажмите здесьcodesandbox

Подпишитесь на официальный аккаунт [IVWEB Community], чтобы еженедельно узнавать о новейших технологиях.