Напишите React-Redux и поэкспериментируйте с контекстным API React

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

Предыдущая статьяМы написали Redux вручную, но чистый Redux — это просто конечный автомат, в нем нет представления пользовательского интерфейса, поэтому, как правило, когда мы его используем, мы будем использовать библиотеку пользовательского интерфейса, такую ​​​​как использование Redux в React.React-Reduxэта библиотека. Что делает эта библиотека, так это связывает конечный автомат Redux и рендеринг пользовательского интерфейса React, когда выdispatch actionИзменятьstateВремя, автоматически обновит страницу. Эта статья также начинается с основ использования его, чтобы написатьReact-ReduxЗатем замените официальную NPM библиотеку и сохраните функциональность последовательными.

Весь код в этой статье был загружен на GitHub, вы можете снять его и поиграть:GitHub.com/Денис — см....

Основное использование

Следующий простой пример представляет собой счетчик, и эффект выглядит следующим образом:

Jul-02-2020 16-44-04

Чтобы реализовать эту функцию, сначала нам нужно добавить в проектreact-reduxбиблиотеку, затем используйте предоставленнуюProviderВесь пакетReactКорневой компонент приложения:

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import store from './store'
import App from './App';

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

Приведенный выше код может видеть, что мы возвращаемProviderпредоставил параметрstore, это параметр ReduxcreateStoreСгенерированоstore, нам нужно вызвать этот метод, а затем вернутьstoreБиография:

import { createStore } from 'redux';
import reducer from './reducer';

let store = createStore(reducer);

export default store;

в приведенном выше кодеcreateStoreПараметр представляет собойreducer, поэтому мы также пишемreducer:

const initState = {
  count: 0
};

function reducer(state = initState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return {...state, count: state.count + 1};
    case 'DECREMENT':
      return {...state, count: state.count - 1};
    case 'RESET':
      return {...state, count: 0};
    default:
      return state;
  }
}

export default reducer;

здесьreduceбудет начальныйstate,внутриcountда0Пока он может справиться с тремяaction,Эти триactionВ соответствии с тремя кнопками пользовательского интерфейса вы можетеstateСчетчик внутри увеличивается, уменьшается и сбрасывается. Вот мы на самом делеReact-Reduxдоступ иReduxОрганизация данных фактически завершена. Если вы хотите использовать ее позжеReduxЕсли данные внутри, просто используйтеconnectAPI будет соответствоватьstateИ метод подключается к компоненту, например, нашему компоненту-счетчику нужноcountЭто состояние и плюс один, минус один, сбросить эти триaction,мы используемconnectПодключите его так:

import React from 'react';
import { connect } from 'react-redux';
import { increment, decrement, reset } from './actions';

function Counter(props) {
  const { 
    count,
    incrementHandler,
    decrementHandler,
    resetHandler
   } = props;

  return (
    <>
      <h3>Count: {count}</h3>
      <button onClick={incrementHandler}>计数+1</button>
      <button onClick={decrementHandler}>计数-1</button>
      <button onClick={resetHandler}>重置</button>
    </>
  );
}

const mapStateToProps = (state) => {
  return {
    count: state.count
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    incrementHandler: () => dispatch(increment()),
    decrementHandler: () => dispatch(decrement()),
    resetHandler: () => dispatch(reset()),
  }
};

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter)

Вы можете увидеть вышеуказанный кодconnectявляется функцией высшего порядка, первый порядок которой получаетmapStateToPropsа такжеmapDispatchToPropsДва параметра, оба из которых являются функциями.mapStateToPropsкоторый можно настроитьstateподключенные к текущему компоненту, эти пользовательскиеstateможно передать в компонентеpropsполучать.mapDispatchToPropsметод пройдет вdispatchфункцию, мы можем настроить некоторые методы, эти методы можно вызватьdispatchидтиdispatch action, что вызываетstateupdate, эти пользовательские методы также можно передать черезpropsполучать,connectПолученный параметр второго порядка является компонентом, мы можем предположить, что функция этой функции заключается в настройке предыдущегоstateМетод и внедряется внутрь сборки, а новый компонент возвращается к внешнему вызову, поэтомуconnectФактически, это также компонент более высокого порядка.

Здесь мы суммируем, какие API мы использовали.Эти API являются целями, которые мы напишем позже:

Provider: компонент, используемый для обертывания корневого компонента, роль которого заключается в внедренииReduxизstore.

createStore: Reduxиспользуется для созданияstoreосновной метод,У нас есть другая статья, переданная рукописными.

connect: используется для преобразованияstateа такжеdispatchВнедрить в требуемый компонент и вернуть новый компонент, который на самом деле является компонентом более высокого порядка.

такReact-ReduxНа самом деле в основе лежат два API, и оба они являются компонентами, и их функции очень похожи, они оба вводят параметры в компоненты.Providerвводится в корневой компонентstore,connectвводится в нужный компонентstateа такжеdispatch.

Прежде чем писать, давайте подумаем, почемуReact-ReduxДля разработки этих двух API, если нет двух API, используйте толькоReduxМогу я? Конечно, это возможно! На самом деле мы используемReduxЦель состоит не в том, чтобы использовать его для сохранения состояния всего приложения, а использовать его только для каждой операции.dispatch actionОбновлять состояние, а потом UI автоматически обновляется? Затем я начинаю с корневого компонента и помещаюstoreМожно ли передать это? Когда каждому подкомпоненту нужно прочитать состояние, используйте его напрямуюstore.getState()Вот и все, просто обновите статусstore.dispatch, это действительно может достичь цели. Однако если так написано, то при наличии множества вложенных уровней подкомпонентов каждый уровень нужно вручную передавать вstore, что некрасиво и громоздко разрабатывать, а если новый одноклассник забудет сдатьstore, после чего последовал ряд ошибок. Так что лучше иметь что-то, что можетstoreГлобально внедрить дерево компонентов, не требуя слой за слоем, какpropsпройти, это вещьProvider! И если каждый компонент зависит независимоReduxуничтожитReactНаправление потока данных, о котором мы поговорим позже.

Контекстный API React

React фактически предоставляет API для глобального внедрения переменных, который является контекстным API. Если теперь у меня есть требование передать конфигурацию цвета текста всем нашим компонентам, наша цветовая конфигурация находится в компоненте верхнего уровня.При изменении цвета все следующие компоненты будут автоматически применять этот цвет. Затем мы можем внедрить эту конфигурацию с помощью контекстного API:

Используйте имяReact.createContextсоздать контекст

// 我们使用一个单独的文件来调用createContext
// 因为这个返回值会被Provider和Consumer在不同的地方引用
import React from 'react';

const TestContext = React.createContext();

export default TestContext;

использоватьContext.Providerобернуть корневой компонент

Создал контекст, если мы хотим передать переменные некоторым компонентам, нам нужно добавить в их корневые компонентыTestContext.Provider, затем поместите переменную какvalueпараметр, переданныйTestContext.Provider:

import TestContext from './TestContext';

const setting = {
  color: '#d89151'
}

ReactDOM.render(
  <TestContext.Provider value={setting}>
  	<App />
  </TestContext.Provider>,
  document.getElementById('root')
);

использоватьContext.Consumerполучить параметры

Выше мы используемContext.ProviderПараметры передаются, так чтоContext.ProviderВсе подкомпоненты пакета могут получить эту переменную, но вам нужно использовать эту переменную, когда вы ее получитеContext.Consumerпакеты, такие как наши предыдущиеCounterКомпонент может получить этот цвет, просто верните егоJSXиспользоватьContext.ConsumerПросто завершите это:

// 注意要引入同一个Context
import TestContext from './TestContext';

// ... 中间省略n行代码 ...
// 返回的JSX用Context.Consumer包裹起来
// 注意Context.Consumer里面是一个方法,这个方法就可以访问到context参数
// 这里的context也就是前面Provider传进来的setting,我们可以拿到上面的color变量
return (
    <TestContext.Consumer>
      {context => 
        <>
          <h3 style={{color:context.color}}>Count: {count}</h3>
          <button onClick={incrementHandler}>计数+1</button>&nbsp;&nbsp;
          <button onClick={decrementHandler}>计数-1</button>&nbsp;&nbsp;
          <button onClick={resetHandler}>重置</button>
        </>
      }
    </TestContext.Consumer>
  );

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

image-20200703171322676

использоватьuseContextполучить параметры

кроме вышеперечисленногоContext.ConsumerМожет быть использован для полученияcontextПараметры, а также новая версия ReactuseContextЭтот хук может получать параметры контекста, что проще в использовании, например, приведенный выше код можно написать так:

const context = useContext(TestContext);

return (
    <>
      <h3 style={{color:context.color}}>Count: {count}</h3>
      <button onClick={incrementHandler}>计数+1</button>&nbsp;&nbsp;
      <button onClick={decrementHandler}>计数-1</button>&nbsp;&nbsp;
      <button onClick={resetHandler}>重置</button>
    </>
);

Таким образом, мы можем использоватьcontext apiпройтиredux store, теперь мы также можем предположитьReact-ReduxизProviderна самом деле это упаковкаContext.Provider, а переданный параметрredux store,а такжеReact-ReduxизconnectHOC на самом деле упаковкаContext.ConsumerилиuseContext. Мы можем сделать это самиReact-Redux.

почеркProvider

сказано вышеProviderиспользовалcontext api, поэтому мы сначала строимcontextфайл для экспортаcontext:

// Context.js
import React from 'react';

const ReactReduxContext = React.createContext();

export default ReactReduxContext;

Этот файл очень простой, создайте новыйcontextПросто экспортируйте его снова.Соответствующий исходный код находится здесь.

тогда поставь этоcontextобратиться к нашемуProviderВнутри компонента:

import React from 'react';
import ReactReduxContext from './Context';

function Provider(props) {
  const {store, children} = props;

  // 这是要传递的context
  const contextValue = { store };

  // 返回ReactReduxContext包裹的组件,传入contextValue
  // 里面的内容就直接是children,我们不动他
  return (
    <ReactReduxContext.Provider value={contextValue}>
      {children}
    </ReactReduxContext.Provider>
  )
}

ProviderКод компонента не сложный, напрямую передайте входящийstoreпомещатьcontext, а затем визуализировать напрямуюchildrenПросто хорошо,Соответствующий исходный код находится здесь.

почеркconnect

основные навыки

фактическиconnectЭто самая сложная часть React-Redux. У него сложные функции и множество факторов, которые необходимо учитывать. Чтобы понять это, нам нужно смотреть на него слой за слоем. Во-первых, мы реализуем базовую функцию, которая имеет только базовые функции.connect.

import React, { useContext } from 'react';
import ReactReduxContext from './Context';

// 第一层函数接收mapStateToProps和mapDispatchToProps
function connect(mapStateToProps, mapDispatchToProps) {
  // 第二层函数是个高阶组件,里面获取context
  // 然后执行mapStateToProps和mapDispatchToProps
  // 再将这个结果组合用户的参数作为最终参数渲染WrappedComponent
  // WrappedComponent就是我们使用connext包裹的自己的组件
  return function connectHOC(WrappedComponent) {

    function ConnectFunction(props) {
      // 复制一份props到wrapperProps
      const { ...wrapperProps } = props;

      // 获取context的值
      const context = useContext(ReactReduxContext);

      const { store } = context;  // 解构出store
      const state = store.getState();   // 拿到state

      // 执行mapStateToProps和mapDispatchToProps
      const stateProps = mapStateToProps(state);
      const dispatchProps = mapDispatchToProps(store.dispatch);

      // 组装最终的props
      const actualChildProps = Object.assign({}, stateProps, dispatchProps, wrapperProps);

      // 渲染WrappedComponent
      return <WrappedComponent {...actualChildProps}></WrappedComponent>
    }

    return ConnectFunction;
  }
}

export default connect;

запустить обновление

используйте вышеуказанноеProviderа такжеconnectзаменить официальноеreact-reduxНа самом деле страница может быть отрендерена, но нажатие на кнопку не будет реагировать, потому что хотя мы и проходимdispatchизмененныйstoreсерединаstate, но это изменение не привело к обновлению нашего компонента. Как упоминалось в статье Redux ранее, вы можете использоватьstore.subscribeконтролироватьstateизменения и выполнить обратный вызов, обратный вызов, который нам нужно зарегистрировать здесь, чтобы проверить, что мы, наконец, даемWrappedComponentизpropsНет изменений, если есть изменения, перерисоватьConnectFunction, так что здесь нам нужно решить две задачи:

  1. когда мыstateКогда изменение проверено, финал даетConnectFunctionпараметры изменились?
  2. Если этот параметр изменится, нам нужно перерендеритьConnectFunction

Проверить изменения параметров

Чтобы проверить изменение параметров, нам нужно знать параметры последнего рендеринга и параметры локального рендера, а затем сравнить их. Для того, чтобы узнать параметры последнего рендера, мы можем напрямуюConnectFunctionвнутри использованиеuseRefЗапишите параметры последнего рендера:

// 记录上次渲染参数
const lastChildProps = useRef();
useLayoutEffect(() => {
  lastChildProps.current = actualChildProps;
}, []);

УведомлениеlastChildProps.currentназначается после первого рендеринга и должен использоватьсяuseLayoutEffectОбеспечить синхронное выполнение сразу после рендеринга.

Поскольку мы обнаруживаем изменения параметров, которые необходимо пересчитатьactualChildProps, логика расчета на самом деле такая же, мы извлекаем эту логику расчета и делаем ее отдельным методомchildPropsSelector:

function childPropsSelector(store, wrapperProps) {
  const state = store.getState();   // 拿到state

  // 执行mapStateToProps和mapDispatchToProps
  const stateProps = mapStateToProps(state);
  const dispatchProps = mapDispatchToProps(store.dispatch);

  return Object.assign({}, stateProps, dispatchProps, wrapperProps);
}

Тогда зарегистрируйтесьstoreОбратный вызов используется для определения того, изменился ли параметр.Если он изменился, текущий компонент принудительно обновляется, и два объекта сравниваются, чтобы увидеть, равны ли они.React-Reduxвнутри используетсяshallowEqual, то есть поверхностное сравнение, то есть только один слой сравнения, если выmapStateToPropsВозвращаются несколько слоев структур, например:

{
  stateA: {
    value: 1
  }
}

ты изменил этоstateA.valueне вызовет повторный рендеринг,React-ReduxЯ думаю, что этот дизайн сделан из соображений производительности.Если это глубокое сравнение, такое как рекурсивное сравнение, это пустая трата производительности, а если есть циклические ссылки, это может вызвать бесконечный цикл. Использование неглубокого сравнения требует, чтобы пользователи следовали этой парадигме, не переходили в многоуровневую структуру,Это также объясняется в официальной документации. Здесь мы напрямую копируем его поверхностное сравнение:

// shallowEqual.js 
function is(x, y) {
  if (x === y) {
    return x !== 0 || y !== 0 || 1 / x === 1 / y
  } else {
    return x !== x && y !== y
  }
}

export default function shallowEqual(objA, objB) {
  if (is(objA, objB)) return true

  if (
    typeof objA !== 'object' ||
    objA === null ||
    typeof objB !== 'object' ||
    objB === null
  ) {
    return false
  }

  const keysA = Object.keys(objA)
  const keysB = Object.keys(objB)

  if (keysA.length !== keysB.length) return false

  for (let i = 0; i < keysA.length; i++) {
    if (
      !Object.prototype.hasOwnProperty.call(objB, keysA[i]) ||
      !is(objA[keysA[i]], objB[keysA[i]])
    ) {
      return false
    }
  }

  return true
}

Обнаружение изменений параметров в обратном вызове:

// 注册回调
store.subscribe(() => {
  const newChildProps = childPropsSelector(store, wrapperProps);
  // 如果参数变了,记录新的值到lastChildProps上
  // 并且强制更新当前组件
  if(!shallowEqual(newChildProps, lastChildProps.current)) {
    lastChildProps.current = newChildProps;

    // 需要一个API来强制更新当前组件
  }
});

Принудительное обновление

Существует более одного способа принудительно обновить текущий компонент, если вы используетеClassкомпонент, вы можете напрямуюthis.setState({}), старая версияReact-ReduxВот что он делает. Но новая версияReact-ReduxПереписано с помощью хука, тогда мы можем использовать React при условииuseReducerилиuseStateкрюк,React-Reduxисходный код используетсяuseReducer, чтобы быть последовательным с ним, я также используюuseReducer:

function storeStateUpdatesReducer(count) {
  return count + 1;
}

// ConnectFunction里面
function ConnectFunction(props) {
  // ... 前面省略n行代码 ... 
  
  // 使用useReducer触发强制更新
  const [
    ,
    forceComponentUpdateDispatch
  ] = useReducer(storeStateUpdatesReducer, 0);
  // 注册回调
  store.subscribe(() => {
    const newChildProps = childPropsSelector(store, wrapperProps);
    if(!shallowEqual(newChildProps, lastChildProps.current)) {
      lastChildProps.current = newChildProps;
      forceComponentUpdateDispatch();
    }
  });
  
  // ... 后面省略n行代码 ...
}

connectЭтот код в основном соответствует исходному кодуconnectAdvancedОсновной принцип и структура этого класса такие же, как и у нас, но он более гибкий и поддерживает передачу пользователями пользовательскихchildPropsSelectorи объединитьstateProps, dispatchProps, wrapperPropsМетоды. Заинтересованные друзья могут пойти и посмотреть его исходный код:GitHub.com/Горячее цинкование это/Горячее…

Здесь мы можем использовать наши собственныеReact-ReduxЗаменена официальная, так же поддерживается функция счетчика. Но я хочу поговорить оReact-ReduxКак обеспечить порядок обновления компонентов, потому что много кода в исходном коде имеет дело с этим.

Гарантированный порядок обновления компонентов

перед нашимCounterИспользование компонентовconnectсвязаныredux store, если под ним есть подкомпонент, который также связан сredux store, мы должны учитывать порядок выполнения их обратных вызовов. Мы знаем, что React — это односторонний поток данных, и параметры передаются от родительских компонентов к дочерним.Redux, даже если и родительский, и дочерний компоненты ссылаются на одну и ту же переменнуюcount, но дочерний компонент может совершенно не брать этот параметр у родительского компонента, а напрямую уReduxВозьми и он сломаетсяReactИсходный поток данных. существует父->子В этом одностороннем потоке данных, если одна из их общедоступных переменных изменяется, сначала должен быть обновлен родительский компонент, а затем параметры передаются дочернему компоненту, а затем обновляются, но вRedux, данные становятсяRedux -> 父,Redux -> 子,а такжеполностью основано наReduxДанные обновляются независимо, и процесс обновления сначала родительского, а затем дочернего не может быть полностью гарантирован. такReact-ReduxПотребовалось много усилий, чтобы вручную гарантировать этот порядок обновлений,React-ReduxСхема, гарантирующая этот порядок обновления, находится вredux storeКроме того, создайте отдельный класс слушателяSubscription:

  1. Subscriptionответственность за всеstateизменить обратный вызов
  2. если в данный момент подключенreduxКомпонент является первым соединениемreduxкомпонент, то есть он связанreduxкорневая составляющая егоstateОбратный вызов регистрируется непосредственно наredux store; также создайте новыйSubscriptionпримерsubscriptionпройти черезcontextпереходил к детям.
  3. если в данный момент подключенreduxКомпоненты не связаныreduxКорневой компонент , то есть на нем есть компоненты, зарегистрированные дляredux store, то он может получить указанный выше проходcontextпереданныйsubscription, переменная в исходном коде называетсяparentSub, обратный вызов обновления текущего компонента регистрируется вparentSubначальство. Также создайте новыйSubscriptionэкземпляр, альтернативаcontextВверхsubscription, продолжить загрузку, то есть обратный вызов его подкомпонента будет зарегистрирован на текущийsubscriptionначальство.
  4. когдаstateизменен, корневой компонент регистрируется наredux storeОбратный вызов выше выполнит обновление корневого компонента, а корневой компонент должен вручную выполнить обратный вызов подкомпонента.Выполнение обратного вызова подкомпонента вызовет обновление подкомпонента, а затем подкомпонент выполнится сам.subscriptionОбратный вызов, зарегистрированный выше, инициирует обновление внучатого компонента, а внучатый компонент вызывает и регистрирует сам себя.subscriptionна обратный звонок. . . Это позволяет начать с корневого компонента и обновлять подкомпоненты слой за слоем, гарантируя, что父->子Такая последовательность обновления.

SubscriptionДобрый

Итак, давайте создадим новыйSubscriptionДобрый:

export default class Subscription {
  constructor(store, parentSub) {
    this.store = store
    this.parentSub = parentSub
    this.listeners = [];        // 源码listeners是用链表实现的,我这里简单处理,直接数组了

    this.handleChangeWrapper = this.handleChangeWrapper.bind(this)
  }

  // 子组件注册回调到Subscription上
  addNestedSub(listener) {
    this.listeners.push(listener)
  }

  // 执行子组件的回调
  notifyNestedSubs() {
    const length = this.listeners.length;
    for(let i = 0; i < length; i++) {
      const callback = this.listeners[i];
      callback();
    }
  }

  // 回调函数的包装
  handleChangeWrapper() {
    if (this.onStateChange) {
      this.onStateChange()
    }
  }

  // 注册回调的函数
  // 如果parentSub有值,就将回调注册到parentSub上
  // 如果parentSub没值,那当前组件就是根组件,回调注册到redux store上
  trySubscribe() {
      this.parentSub
        ? this.parentSub.addNestedSub(this.handleChangeWrapper)
        : this.store.subscribe(this.handleChangeWrapper)
  }
}

SubscriptionСоответствующий исходный код находится здесь.

МодернизацияProvider

а потом реализовали сами до насReact-ReduxВнутри наш корневой компонент всегдаProvider,такProviderнеобходимо создать экземплярSubscriptionи положиcontextна, и каждый разstateПри обновлении необходимо вручную вызвать обратный вызов подкомпонента, модификация кода выглядит следующим образом:

import React, { useMemo, useEffect } from 'react';
import ReactReduxContext from './Context';
import Subscription from './Subscription';

function Provider(props) {
  const {store, children} = props;

  // 这是要传递的context
  // 里面放入store和subscription实例
  const contextValue = useMemo(() => {
    const subscription = new Subscription(store)
    // 注册回调为通知子组件,这样就可以开始层级通知了
    subscription.onStateChange = subscription.notifyNestedSubs
    return {
      store,
      subscription
    }
  }, [store])

  // 拿到之前的state值
  const previousState = useMemo(() => store.getState(), [store])

  // 每次contextValue或者previousState变化的时候
  // 用notifyNestedSubs通知子组件
  useEffect(() => {
    const { subscription } = contextValue;
    subscription.trySubscribe()

    if (previousState !== store.getState()) {
      subscription.notifyNestedSubs()
    }
  }, [contextValue, previousState])

  // 返回ReactReduxContext包裹的组件,传入contextValue
  // 里面的内容就直接是children,我们不动他
  return (
    <ReactReduxContext.Provider value={contextValue}>
      {children}
    </ReactReduxContext.Provider>
  )
}

export default Provider;

Модернизацияconnect

имеютSubscriptionДобрый,connectне могу зарегистрироваться напрямуюstoreвместо этого он должен быть зарегистрирован в родительскомsubscriptionВ приведенном выше примере при обновлении помимо самого обновления он также уведомляет подкомпоненты об обновлении. При рендеринге обернутого компонента его нельзя рендерить напрямую, но его следует использовать снова.Context.ProviderПод пакетом вставьте модифицированныйcontextValue,этоcontextValueвнутриsubscriptionследует заменить на свой. Модифицированный код выглядит следующим образом:

import React, { useContext, useRef, useLayoutEffect, useReducer } from 'react';
import ReactReduxContext from './Context';
import shallowEqual from './shallowEqual';
import Subscription from './Subscription';

function storeStateUpdatesReducer(count) {
  return count + 1;
}

function connect(
  mapStateToProps = () => {}, 
  mapDispatchToProps = () => {}
  ) {
  function childPropsSelector(store, wrapperProps) {
    const state = store.getState();   // 拿到state

    // 执行mapStateToProps和mapDispatchToProps
    const stateProps = mapStateToProps(state);
    const dispatchProps = mapDispatchToProps(store.dispatch);

    return Object.assign({}, stateProps, dispatchProps, wrapperProps);
  }

  return function connectHOC(WrappedComponent) {
    function ConnectFunction(props) {
      const { ...wrapperProps } = props;

      const contextValue = useContext(ReactReduxContext);

      const { store, subscription: parentSub } = contextValue;  // 解构出store和parentSub
      
      const actualChildProps = childPropsSelector(store, wrapperProps);

      const lastChildProps = useRef();
      useLayoutEffect(() => {
        lastChildProps.current = actualChildProps;
      }, [actualChildProps]);

      const [
        ,
        forceComponentUpdateDispatch
      ] = useReducer(storeStateUpdatesReducer, 0)

      // 新建一个subscription实例
      const subscription = new Subscription(store, parentSub);

      // state回调抽出来成为一个方法
      const checkForUpdates = () => {
        const newChildProps = childPropsSelector(store, wrapperProps);
        // 如果参数变了,记录新的值到lastChildProps上
        // 并且强制更新当前组件
        if(!shallowEqual(newChildProps, lastChildProps.current)) {
          lastChildProps.current = newChildProps;

          // 需要一个API来强制更新当前组件
          forceComponentUpdateDispatch();

          // 然后通知子级更新
          subscription.notifyNestedSubs();
        }
      };

      // 使用subscription注册回调
      subscription.onStateChange = checkForUpdates;
      subscription.trySubscribe();

      // 修改传给子级的context
      // 将subscription替换为自己的
      const overriddenContextValue = {
        ...contextValue,
        subscription
      }

      // 渲染WrappedComponent
      // 再次使用ReactReduxContext包裹,传入修改过的context
      return (
        <ReactReduxContext.Provider value={overriddenContextValue}>
          <WrappedComponent {...actualChildProps} />
        </ReactReduxContext.Provider>
      )
    }

    return ConnectFunction;
  }
}

export default connect;

сюда нашReact-ReduxГотово, эффект от запуска такой же, как и у официального, полный код выложен на GitHub:GitHub.com/Денис — см....

Ниже мы резюмируемReact-Reduxосновной принцип.

Суммировать

  1. React-ReduxподключенReactа такжеReduxбиблиотека, при использованииReactа такжеReduxAPI.
  2. React-Reduxв основном используетсяReactизcontext apiпройтиReduxизstore.
  3. Providerэто получитьRedux storeи поместите его вcontextпередай.
  4. connectРоль изRedux storeВыберите необходимые свойства для передачи компоненту-оболочке.
  5. connectОн сам рассудит, нужно ли его обновлять, и нужна ли основа для суждения.stateизменилось.
  6. connectПри оценке того, есть ли изменение, используется поверхностное сравнение, то есть сравнивается только один слой, поэтому вmapStateToPropsа такжеmapDispatchToPropsНе возвращайте многоуровневые вложенные объекты.
  7. Чтобы решить независимые зависимости родительских компонентов и дочерних компонентовRedux,сломанныйReactиз父级->子级процесс обновления,React-ReduxиспользоватьSubscriptionСам класс управляет процессом уведомления.
  8. подключаться только кReduxКомпоненты верхнего уровня будут зарегистрированы непосредственно вRedux store, другие дочерние компоненты будут зарегистрированы в ближайшем родительском компонентеsubscriptionна экземпляре.
  9. При уведомлении он, в свою очередь, уведомляет свои собственные подкомпоненты от корневого компонента.Когда подкомпонент получает уведомление, он сначала обновляет себя, а затем уведомляет свои собственные подкомпоненты.

использованная литература

Официальная документация:react-redux.js.org/

Исходный код GitHub:GitHub.com/Горячее цинкование это/Горячее…

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

Добро пожаловать, чтобы обратить внимание на мой общедоступный номербольшой фронт атакиПолучите высококачественные оригиналы впервые~

Цикл статей "Передовые передовые знания":nuggets.capable/post/684490…

Адрес GitHub с исходным кодом из серии статей «Advanced Front-end Knowledge»:GitHub.com/Денис — см....