Повторно реализовать react-redux с помощью хуков в 70 строках кода

внешний интерфейс Chrome React.js Redux

причина

react-hooks — это официальная новая рекомендация по написанию реакции Мы можем легко инкапсулировать очень простой слой в официальном хуке useReducer для достижения функций, аналогичных предыдущему react-redux \ redux-thunk \ redux-logger, и значительно упростить оператор.

Подробнее о реактивных хукахreactjs.org/hooks;

Сначала посмотрите на источник

  • Простая реализация react-redux, redux-thunk и redux-logger
  • Стиль редуктора по умолчанию, также может объявить традиционный редуктор стиля
import React from 'react';

function middlewareLog(lastState, nextState, action, isDev) {
  if (isDev) {
    console.log(
      `%c|------- redux: ${action.type} -------|`,
      `background: rgb(70, 70, 70); color: rgb(240, 235, 200); width:100%;`,
    );
    console.log('|--last:', lastState);
    console.log('|--next:', nextState);
  }
}

function reducerInAction(state, action) {
  if (typeof action.reducer === 'function') {
    return action.reducer(state);
  }
  return state;
}

export default function createStore(params) {
  const { isDev, reducer, initialState, middleware } = {
    isDev: false,
    reducer: reducerInAction,
    initialState: {},
    middleware: params.isDev ? [middlewareLog] : undefined,
    ...params,
  };
  const AppContext = React.createContext();
  const store = {
    isDev,
    _state: initialState,
    useContext: function() {
      return React.useContext(AppContext);
    },
    dispatch: undefined,
    getState: function() {
      return store._state;
    },
    initialState,
  };
  let isCheckedMiddleware = false;
  const middlewareReducer = function(lastState, action) {
    let nextState = reducer(lastState, action);
    if (!isCheckedMiddleware) {
      if (Object.prototype.toString.call(middleware) !== '[object Array]') {
        throw new Error("react-hooks-redux: middleware isn't Array");
      }
      isCheckedMiddleware = true;
    }
    for (let i = 0; i < middleware.length; i++) {
      const newState = middleware[i](store, lastState, nextState, action);
      if (newState) {
        nextState = newState;
      }
    }
    store._state = nextState;
    return nextState;
  };

  const Provider = props => {
    const [state, dispatch] = React.useReducer(middlewareReducer, initialState);
    if (!store.dispatch) {
      store.dispatch = async function(action) {
        if (typeof action === 'function') {
          await action(dispatch, store._state);
        } else {
          dispatch(action);
        }
      };
    }
    return <AppContext.Provider {...props} value={state} />;
  };
  return { Provider, store };
}

редюсер в действии — это функция редуктора, и эти 6 строк кода — все, что нужно для редуктора в действии:

function reducerInAction(state, action) {
  if (typeof action.reducer === 'function') {
    return action.reducer(state);
  }
  return state;
}

Он упрощает редюсер и помещает его в каждое действие для обработки редуктора. Нам больше не нужно писать кучу переключателей, и нам больше не нужно всегда обращать внимание на то, соответствует ли тип действия типу в redcer.

С редьюсером в действии и стилем thunk редукс можно написать очень просто, при сложности проекта нам нужно написать только действия, что сделает структуру проекта более понятной.

Установить

Установитьreact-hooks-reduxтребуется версия React> = 16,7

yarn add react-hooks-redux

использовать

Мы объявили полный пример реакции-редукции менее чем в 35 строк кода, включая хуки.

import React from 'react';
import ReactHookRedux from 'react-hooks-redux';

// 通过 ReactHookRedux 获得 Provider 组件和一个 sotre 对象
const { Provider, store } = ReactHookRedux({
  isDev: true, // 打印日志
  initialState: { name: 'dog', age: 0 },
});

function actionOfAdd() {
  return {
    type: 'add the count',
    reducer(state) {
      return { ...state, age: state.age + 1 }; // 每次需要返回一个新的 state
    },
  };
}

function Button() {
  function handleAdd() {
    store.dispatch(actionOfAdd()); //dispatch
  }
  return <button onClick={handleAdd}>add</button>;
}

function Page() {
  const state = store.useContext();
  return (
    <div>
      {state.age} <Button />{' '}
    </div>
  );
}

export default function App() {
  return (
    <Provider>
      <Page />
    </Provider>
  );
}

В заключение:

  • Готов к работе
    • Создайте компонент Provider и сохраните объект с помощью ReactHookRedux
    • Оберните корневой компонент с помощью Provider
  • использовать
    • Используйте store.useContext(), чтобы получить состояние в магазине, где вам нужно использовать состояние
    • Отправка обновлений с помощью store.dispatch(action())

Мы читаем этот небольшой пример найдут, что для компонента нет подключения. Там нет функции редуктора. Этот упрощенный дизайн - это черкарри крючки, крючки значительно упрощают нас, чтобы приготовить тысячу панельных шаблонов, но если нам все еще нужно подключить, мы Вернулся к старому способу записи кода шаблона.

Написание промежуточного программного обеспечения

Давайте посмотрим на исходный код промежуточного программного обеспечения:

let nextState = reducer(lastState, action);
for (let i = 0; i < middleware.length; i++) {
  const newState = middleware[i](lastState, nextState, action, isDev);
  if (newState) {
    nextState = newState;
  }
}
return nextState;

Наибольшая разница в производительности (и внедрении) заключается в том, что React-Cloks-redux использует крючок USEContext вместо компонента более высокого порядка CONCEN для отправки.

В традиционном react-redux, если компонент обрабатывается функцией connect более высокого порядка, то при отправке будет выполняться связанная с компонентом функция mapStateToProps и возвращать новые реквизиты для активации обновлений компонента.

В стиле хуков, когда компонент объявляется useContext(), объект, связанный с контекстом, изменяется, и компонент будет обновлен.

Теоретически производительность такая же, как и у react-redux, поскольку у хуков меньше объявлений, чем у классов, они должны быть быстрее.

Итак, у нас есть измененное использование UseContext, чтобы уменьшить количество компонентов, которые должны обновляться Dispatch.

Если нам нужно вручную контролировать сокращение обновлений, мы можем обратиться кuseMemoКак использовать крючок, чтобы соответствовать.

Если вы не хотите, чтобы компонент обновлялся с помощью store.dispatch(), вы можете использовать store.getState() только для чтения данных, что также может уменьшить количество ненужных обновлений компонентов.

Все вышеизложенное является теоретическим анализом.Поскольку эта библиотека и этот документ являются продуктом поздней ночи и не проводили тестов производительности, любой, кто готов помочь сделать некоторые тесты производительности, очень приветствуется.

другие примеры

По ходу работы были улучшены некоторые функции, а также количество кода возросло до 300 строк.Если вам интересно, то можете зайти на склад посмотреть:

  • Подписаться Добавить прослушиватель
  • Например, используйте соглашение об автосохранении для кэширования состояния и чтения.
  • middlewareLog может печатать неизменяемые объекты и другие функции, связанные с управлением состоянием.

Пример асинхронного действия и кэширования состояния в браузере

import React from 'react';
import ReactHookRedux, {
  reducerInAction,
  middlewareLog,
} from 'react-hooks-redux';

// 通过 ReactHookRedux 获得 Provider 组件和一个 sotre 对象
const { Provider, store } = ReactHookRedux({
  isDev: true, // default is false
  initialState: { count: 0, asyncCount: 0 }, // default is {}
  reducer: reducerInAction, // default is reducerInAction 所以可省略
  middleware: [middlewareLog], // default is [middlewareLog] 所以可省略
  actions: {}, // default is {} 所以可省略
  autoSave: {
    item: 'localSaveKey',
    keys: ['user'], // 需要缓存的字段
  },
});

// 模拟异步操作
function timeOutAdd(a) {
  return new Promise(cb => setTimeout(() => cb(a + 1), 500));
}

const actions = {
  // 如果返回的是一个function,我们会把它当成类似 react-thunk 的处理方式,并且额外增加一个ownState的对象方便获取state
  asyncAdd: () => async (dispatch, ownState) => {
    const asyncCount = await timeOutAdd(ownState.asyncCount);
    dispatch({
      type: 'asyncAdd',
      // if use reducerInAction, we can add reducer Function repeat reducer
      reducer(state) {
        return {
          ...state,
          asyncCount,
        };
      },
    });
  },
};

function Item() {
  const state = store.useContext();
  return <div>async-count: {state.asyncCount}</div>;
}

function Button() {
  async function handleAdd() {
    // 使用 async dispatch
    await store.dispatch(actions.asyncAdd());
  }
  return <button onClick={handleAdd}>add</button>;
}

export default function App() {
  return (
    <Provider>
      <Item />
      <Button />
    </Provider>
  );
}

Пример использования immutableJS с хуками для уменьшения повторного рендеринга

import React, { useCallback } from 'react';
import ReactHookRedux from 'react-hooks-redux';
import { Map } from 'immutable';

const { Provider, store } = ReactHookRedux({
  initialState: Map({ products: ['iPhone'] }), // 请确保immutable是一个Map
  isDev: true, // 当发现对象是 immutable时,middleware会遍历属性,使用getIn做浅比较打印 diff的对象
});

function actionAddProduct(product) {
  return {
    type: 'add the product',
    reducer(state) {
      return state.update('products', p => {
        p.push(product);
        return [...p];
      });
    },
  };
}

let num = 0;
function Button() {
  function handleAdd() {
    num += 1;
    store.dispatch(actionAddProduct('iPhone' + num)); //dispatch
  }
  return <button onClick={handleAdd}>add-product</button>;
}

function Page() {
  const state = store.useContext();
  // 从immutable获取对象,如果products未改变,会从堆中获取而不是重新生成新的数组
  const products = state.get('products');

  return useCallback(
    <div>
      <Button />
      {products.map(v => (
        <div>{v}</div>
      ))}
    </div>,
    [products], // 如果products未发生改变,不会进行进行重渲染
  );
}

export default function App() {
  return (
    <Provider>
      <Page />
    </Provider>
  );
}

Спасибо за прочтение.