[KT] легко получить интерпретацию исходного кода REDUX и искусство программирования

React.js

предисловие

📢 Первый блог:Блог Ах Куана

Прежде чем начать эту статью, позвольте мне сказать несколько слов, то есть эта статья немного длинная, и есть некоторые исходники и т. д.;Хан ЯнСтарик немного поболтал, и он сказал, что теперь я понимаю, как писать статьи, и предложил мне стабилизироваться на следующем этапе, а затем писать что-то с глубиной, а не плавать на поверхности; я слушал ту же статью в прошлую субботу Компания опубликовалаМастер Чжан, который копает ямуобмен техническими текстами.

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

❓ Почему эта колонка называется [KT], я относительно невысокий человек, а китайское название колонки: Глубокая статья о технологии Куан, К взято из Куан, Т, Технология, Технология, и имеет сильный стиль. Хорошо, я чувствую, что продвинул свою технологию хвастовства на шаг дальше.

Эта статья конвейера

Из-за временных отношений в группе была введена дискуссия об управлении состоянием в реакции, вращающаяся вокругхокс, мобкс, редуксЕсть волна обменов, поэтому практическая практика четвертого шага, я обновлю ее позже и планирую изучить ее в следующий период времени.hox,mobxОдин из принципов внутренней реализации, а потом написать демо с практической практикой, просмотреть его в группе, взять суть и избавиться от шлака, может это новый продукт? Рад думать об этом~

Не распыляйте, делайте колеса, чтобы научиться~

Эта статья для людей

  • 🍉 Ешь дыню толпой
  • Игрок начального уровня Redux
  • Хотите узнать внутреннюю историю Redux
  • Интересно об искусстве программирования Redux

Что вы узнаете после прочтения этой статьи

  • охваченный наукой函数式编程,洋葱模型связанная информация
  • Sexy Xiaopeng, отведи тебя, чтобы увидеть исходный код Redux
  • Узнайте о некоторых философиях дизайна в библиотеке Redux.
  • Больше не боитесь, что интервьюер спросит вас, почему redux возвращает новое состояние
  • (Я гарантирую, что следующая часть определенно будет практической!!!)

текст начинается

Введение

Когда блогер проходил собеседование в конце 2018 года, интервьюер посмотрел мое резюме и спросил: «Я видел ваше резюме, использовались и vue, и react, можете ли вы мне сказатьВ чем разница между Vue и React?«В то время я заставил Лай Лая сказать это, и я не знал, прав ли он. Затем, когда дело дошло до vuex и redux, произошло кровавое убийство. Интервьюер спросил, почему Redux всегда возвращается к новое состояние? Почему я не могу вернуться в старое состояние? Излишне говорить, что результаты интервью, в конце концов, я не знал так много в то время~

После интервью я нашел время, чтобы прочитать исходный код редукса, ojbk, у меня действительно закружилась голова, я помню, что когда я смотрел его, редукс еще не вводил TS, Некоторое время назад я хотел узнать о нем больше.redux, Кто знает, это вышло из-под контроля, черт его знает, сколько слов я сказал в процессе просмотра WC, офигенно...

Хотя эта статья написана для начинающих игроков в редукс, из-за моего проклятого чувства ритуала я все же должен дать краткое введение, прежде чем что-то сказать~

Что такое редукс?

Redux — это контейнер состояния JavaScript, предоставляющий решение для предсказуемого управления состоянием, как описано на официальном сайте:

✋ Redux — это контейнер с предсказуемым состоянием для приложений JavaScript.

баа? я не понимаю? Подождите минутку, прежде чем я объясню, позвольте мне задать вам вопрос,Является ли реакция односторонним или двусторонним потоком данных?, если ваш ответ двусторонний поток данных, окей, пока 👋, поверните налево, когда будете выходить, если вы ответите односторонний поток данных, что ж, мы все еще хорошие братья~

Чтобы понять, что такое редукс, сначала посмотрите на картинку, которую я нарисовал 👇

Мы знаем, что в реакции есть свойства и состояние.Когда мы хотим передать данные из родительского компонента в дочерний компонент, мы можем передавать данные через свойства.Если мы хотим управлять состоянием внутри компонента, мы можем использовать состояние. Однако мы проигнорировали собственные чувства реакции~

реагировать Это в форме одностороннего потока данных, у него нет возможности откатить данные, вы либо распределяете его по нисходящей, либо управляете им сами. (Почему, а как насчет оспаривания авторитета? Как вы думаете, вы можете совершить следующее?)

Когда Сяо Пэн услышал это: «О, это неправильно, нельзя ли изменить состояние родительского компонента с помощью обратных вызовов?» Да, это действительно возможно. Давайте поговорим о том, почему мы используем redux.Вообще говоря, мы можем использовать redux в наших проектах, почти все они представляют собой законченное приложение. В настоящее время, если вы хотите общаться между двумя родственными компонентами, сплетничать и обмениваться данными, что вам следует делать?

Мы моделируем сценарий, в котором компонент Пэн и компонент Куан хотят делиться и обмениваться некоторыми данными друг с другом.Согласно одностороннему потоку данных реагирования, как его решить?

Эта картина должна быть понятна, то есть наши компоненты-братья хотят общаться друг с другом и обмениваться данными друг друга, тогда единственное решение:состояние повышения, состояние исходных компонентов Peng и Kuan передается общему родительскому компоненту для управления, а затем родительский компонент передает данные вниз. Дочерний компонент обрабатывает, а затем функция обратного вызова возвращает измененное состояние, которое в определенной степени реагирует.

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

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

Некоторые друзья предположили, что Provider это что-то из библиотеки react-redux.Да это котел А Куана.Поскольку вышеизложенное подсознательно связывает react с redux,то это вызвало у всех недоразумение!

💥 Повторюсь, редукс не имеет ничего общего с реакцией!!!

Вот почему мыreactВ проекте всегда видно, что в корневом компоненте App есть такая штука.

function App() {
  return (
    <Provider store={store}>
      ...
    </Provider>
  );
}

Три принципа

Акуан здесь по умолчанию указывает, что все будут использовать избыточность. Если вы не знаете, как ее использовать, вы можете перейти к документации и написать демо, и вы это узнаете. Тем не менее, я все же хочу поговорить о трех принципах избыточности~

  • Единый источник достоверной информации: для всего приложенияstateхранятся в дереве состояний и существуют только вТольков магазине
  • состояние доступно только для чтения: единственный способ изменить состояние — запуститьaction, а затем отправьте диспетчер через тип действия. Невозможно напрямую изменить состояние приложения
  • Изменения статуса вносятся纯函数Завершение: Чтобы описать, как действие изменяет дерево состояний, напишитеreducers

Базовый запас знаний

Store

storeпредоставляется ReduxcreateStore(reducers, preloadedState, enhancer)метод генерации. Из сигнатуры функции для создания хранилища необходимо передать редукторы, а также можно передать второй необязательный параметр состояния инициализации (preloadedState).

Третий параметр вообще промежуточное ПОapplyMiddleware(thunkMiddleware), посмотрите на код, он более интуитивен

import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk' // 这里用到了redux-thunk

const store = createStore(
  reducerList,
  (initialState = {}),
  applyMiddleware(thunkMiddleware)
)

Основной API в редукции:createStore, хранилище, созданное методом createStore, представляет собой объект, который сам содержит 4 метода:

  • getState(): Получить текущее состояние в магазине.
  • subscribe(listener) : зарегистрируйте прослушиватель, который вызывается при изменении хранилища.
  • Отправка (действие): отправьте действие и вернуть это действие, это единственный способ изменить данные в магазине.
  • replaceReducer(nextReducer) : обновите редуктор в текущем хранилище, обычно вызывайте этот метод только в режиме разработки.

Aciton

Действия — это полезные данные, которые передают данные из приложения в магазин. Это единственный источник данных магазина. Проще говоря, Action — это тип сообщения, которое сообщает Redux, что делать, когда пришло время это сделать, и передает его в Redux с соответствующими данными.

Действие — это простой объект, который должен иметь атрибут типа, который используется для обозначения типа действия (редюсер определяет выполняемую логику), а другие атрибуты могут быть настроены пользователем. Такие как:

const KUAN_NEED_GRID_FRIEND = 'KUAN_NEED_GRID_FRIEND'
// 一个action对象
// 比如此action是告诉redux,阿宽想要一个女朋友
{
  type: KUAN_NEED_GRID_FRIEND,
  params: {
    job: '程序员',
    username: '阿宽'
  }
}

Давайте поймем точку знаний:Action Creator, посмотрите введение на официальном сайте: Action Creator в Redux просто возвращает действие, которое мы обычно пишем так~

function fetchWishGridFriend(params, callback) {
  return {
    type: KUAN_NEED_GRID_FRIEND,
    params,
    callback,
  }
}

Мы знаем, что Redux развился из Flux.В традиционном Flux создатели действий часто запускают отправку после вызова. Вот так 👇

// 传统 Flux
function fetchFluxAction(params, callback) {
  const action = {
    type: KUAN_NEED_GRID_FRIEND,
    params,
    callback,
  }
  dispatch(action)
}

Но в редуксе, поскольку в магазине есть метод отправки (упомянутый выше), нам нужно толькоAction CreatorsВозвращенный результат передаетсяdispatch(), процесс инициирования отправки завершен, и даже созданы привязанные создатели действий для автоматической отправки ~

// 普通dispatch
store.dispatch(fetchWishGridFriend(params, () => {}))

// 绑定dispatch
const bindActionCreatorsDemo = (params, callback) => (store.dispatch) =>
  store.dispatch(fetchWishGridFriend(params, callback))
bindActionCreatorsDemo() // 就能实现一个dispatch action

👉 В вашем коде вы точно найдете этоbindActionCreators()Дело в том, что, поскольку обычно мы будем использовать хелпер connect(), предоставляемый react-redux, bindActionCreators() может автоматически привязывать несколько функций создания действий к методу dispatch().

Reducers

Редюсеры должны быть чистой функцией, которая обрабатывает обновления состояния на основе действий, возвращая старое состояние, если нет обновления или встречает неизвестное действие; в противном случае она возвращает новый объект состояния.Примечание. Вы не можете изменить старое состояние, вы должны сначала скопировать состояние, а затем изменить его, вы также можете использовать функцию Object.assign для создания нового состояния., в частности, почему, мы знаем, когда читаем исходный код~

Например 🌰

// 用户reducer
const initialUserState = {
    userId: undefined
}

function userReducer = (state = initialUserState, action) {
  switch(action.type) {
    case KUAN_NEED_GRID_FRIEND:
      return Object.assign({}, state, {
        userId: action.payload.data
      })
    default:
      return state;
  }
}

Перед просмотром исходного кода поставлю яркий 🌰, чтобы всем было понятно.

Сяопэн хочет взять отпуск для поездки.Согласно первоначальному процессу, Сяопэн должен подать заявление на отпуск от Сяопина -> пропуска менеджера отдела -> пропуска технического директора -> пропуска отдела кадров (односторонний процесс), отпускной лист Сяопина не может быть направлен непосредственно ЧР. Смотрите картинку ниже 👇

Когда Акуан увидел, что Сяопэн просит разрешения на поездку, он также хотел попросить волну, поэтому он хотел скопировать причину, по которой Сяопэн попросил об отпуске (братские компоненты обмениваются данными), что мне делать? Он не может получить данные напрямую от Сяопэна, так что он может быть только глупым. Через менеджера отдела и технического директора он «преодолел барьер» вплоть до отдела кадров, указал на отдел кадров и сказал: «Вы можете сделать копию формы заявления на отпуск Сяо Пэна, и я сделаю также отпроситься.

Сяо Пэн и А Куан хотят обмениваться данными только через общего босса (HR)

Когда мы используем редукс, это становится таким 👇 Я понимаю кнопку 1, но я не могу понять бусины для глаз кнопок

Получить исходный код

Ган!!!Я пришел к интерпретации исходного кода, которую ненавижу больше всего, потому что слишком сложно говорить об исходном коде, не исходный код сложен, а как говорить о нем сложнее, в конце концов, то, что я понимаю и знать о редуксе не обязательно правильно, и в то же времяЯ не хочу вставлять много кода напрямую, разве вы не прочитали эту статью только потому, что не хотели читать исходный код?~

Но никак, да здравствует понимание. К счастью, файлов с исходным кодом redux относительно немного, давайте вместе!

🎉 Посмотрите прямо на исходный код,гитхаб нажмите здесь, мы можем увидеть такую ​​файловую структуру

├── utils
│   ├── actionTypes
│   ├── isPlainObject
│   ├── warning
│   └─
│
├── applyMiddleware
├── bindActionCreatorts
├── combineReducers
├── compose
├── createStore
├── index.js
│
└─

Не так много, верно? Если вы говорите слишком много, выйдите и поверните налево. Чтобы увидеть исходный код изindex.jsНачнем, следим за камерой, посмотрим, что в этом файле. По сути ничего важного, просто импортировать файл и экспортировать его

// index.js
import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
...

export { createStore, combineReducers, bindActionCreators, applyMiddleware, compose }

Давайте сначала посмотрим на первую строку кода,import createStore from './createStore', 😯, я это знаю, разве это не один из самых основных API в редуксе? Давайте разоблачим это~

createStore на первом месте

// API
const store = createStore(reducers, preloadedState, enhance)

На первый взгляд, я не знаю, что означают эти три параметра? Не паникуйте, сначала выкурите сигарету, откройте Baidu Translate, и вы все узнаете. (Поскольку эти три параметра объясняются в исходном коде)

/**
 * 创建一个包含状态树的Redux存储
 * 更改store中数据的唯一方法是在其上调用 `dispatch()`
 *
 * 你的app中应该只有一个store,指定状态树的不同部分如何响应操作
 * 你可以使用 `combineReducers` 将几个reducer组合成一个reducer函数
 *
 * @param {Function} reducer 给定当前状态树和要处理的操作的函数,返回下一个状态树
 *
 * @param {any} [preloadedState] 初始状态. 你可以选择将其指定为中的universal apps服务器状态,或者还原以前序列化的用户会话。
 * 如果你使用 `combineReducers` 来产生 root reducer 函数,那么它必须是一个与 `combineReducers` 键形状相同的对象
 *
 * @param {Function} [enhancer] store enhancer. 你可以选择指定它来增强store的第三方功能
 * 比如 middleware、time travel、persistence, Redux附带的唯一商店增强器是 `applyMiddleware()`
 *
 * @returns {Store} Redux Store,允许您读取状态,调度操作和订阅更改。
 */

Поняв значение этих трех параметров, давайте посмотрим на его возвращаемое значение.Не беспокойтесь о том, что вы делаете в середине. Как было сказано выше, звонитеcreateStoreХранилище, созданное методом, представляет собой объект, который содержит 4 метода, поэтому код должен быть таким, а не я!

// createStore.js
export default function createStore(reducer, preloadedState, enhancer) {
  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false // 是否正在分发事件

  function getState() {
    // ...
    return currentState
  }

  function subscribe(listener) {
    // ...
  }

  function dispatch(action) {
    // ...
    return action
  }

  function replaceReducer(nextReducer) {
    // ...
  }

  function observable() {
    // ...
  }

  dispatch({ type: ActionTypes.INIT })

  // ...
  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [?observable]: observable,
  }
}

дизайн песочницы

Что касается этих кодов, я должен их понимать, но я должен восхищаться человеком, который написал этот код! ! сначала черезЗакрытиеВнутренние переменные приватизированы, а внешние переменные недоступны в замыкании. Во-вторых, он предоставляет интерфейс для получения внешнего доступа к внутренним свойствам.

Это не песочница? Песочница позволяет вашей программе работать в изолированной среде, не затрагивая другие программы во внешнем мире. нашcreateStoreОбеспечьте внутреннюю защиту внутренних данных, а также внешний доступ и работу через разработанный интерфейс. 🐂 🍺 ~

subscribe/dispatch

💥 Рекомендуется сразу перейти к файлу с исходным кодом, потому что комментарии к каждому интерфейсу очень подробные~

Нетрудно увидеть, пройти вышеsubscribeЧто касается функции регистрации интерфейса и подписки, мы можем подробнее рассмотреть, что делает эта функция~

function subscribe(listener) {
  ...

  let isSubscribed = true

  ensureCanMutateNextListeners();
  nextListeners.push(listener)

  return function unsubscribe() {
    if(!isSubscribed) {
      return
    }

    // reducer执行中,你可能无法取消store侦听器
    if (isDispatching) {}

    isSubscribed = false

    // 从 nextListeners 中去除掉当前 listener
    ensureCanMutateNextListeners()
    const index = nextListeners.indexOf(listener)
    nextListeners.splice(index, 1)
}

На самом деле, главное, что делает этот метод, это:Зарегистрируйте прослушиватель и верните метод для отмены регистрации события. Вызов слушателя при вызове store.dispatch ~

Идея действительно строгая, определениеisSubscribed,isDispatchingчтобы избежать непредвиденных ситуаций, а также защитить входящую паруlisterТиповое суждение. Учитывая, что некоторые люди отпишутся, есть еще и отпискаunsubscribe.

Тогда давайте посмотримdispatch, в основном используется для публикации объекта действия, как упоминалось ранее, если вы хотите изменить данные в хранилище, единственный способ — отправить действие, давайте посмотрим, что оно делает~

function dispatch(action) {
  if (!isPlainObject(action)) {
  }

  if (typeof action.type === 'undefined') {
  }

  // 调用dispatch的时候只能一个个调用,通过dispatch判断调用的状态
  if (isDispatching) {
  }

  try {
    isDispatching = true
    currentState = currentReducer(currentState, action)
  } finally {
    isDispatching = false
  }

  // 遍历调用各个listener
  const listeners = (currentListeners = nextListeners)
  for (let i = 0; i < listeners.length; i++) {
    const listener = listeners[i]
    listener()
  }
  return action
}

Нет-с, это так строго, и разные ограничения и суждения сделаны спереди.try {} finally {}Это также действие Бога, чтобы гарантировать, чтоisDispatchКогда внутреннее состояние функции согласовано, оно будет изменено на окончательноfalse. говядина~

Из комментариев к исходникам я тоже видел такой абзац ~

It will be called any time an action is dispatched, and some part of the state tree may potentially have changed.

You may then call getState() to read the current state tree inside the callback.

иметь в виду,После того, как вы выполните ранее подписанный слушатель функции, вы должны,store.getState()перейти к последним данным. Поскольку этот слушатель функции подписки не имеет параметров, он очень строгий.

bindActionCreators

Г-н Лао Шэ «Четыре поколения вместе» в 19-м предложении: «Он чувствовал, что босс очень милый, поэтому он решилкуй железо пока горячо, высказываться. ", да, куйте железо, пока горячо, раз уж мы говорили оdispatch(action), тогда поговорим об этом:bindActionCreators~

Я не знаю, написали ли вы такой код...

import { bindActionCreators } from 'redux';
import * as pengActions from '@store/actions/peng';
import * as kuanActions from '@store/actions/kuan';
import * as userActions from '@store/actions/user';

const mapDispatchToProps => dispatch => {
  return {
    ...bindActionCreators(pengActions, dispatch);
    ...bindActionCreators(kuanActions, dispatch);
    ...bindActionCreators(userActions, dispatch);
  }
}

Скажем такbindActionCreatorsчто именно он делает. Сначала взгляните на официальные комментарии к исходному коду:

  • Преобразование объекта, значением которого являются создатели действий, в объект с тем же ключом
  • оберните каждую функцию какdispatchпозвонить, чтобы их можно было вызвать напрямую
  • Конечно, вы также можете позвонитьstore.dispatch(MyActionCreator.doSomething)
function bindActionCreator(actionCreator, dispatch) {
  return function (this, ...args) {
    return dispatch(actionCreator.apply(this, args))
  }
}

// bindActionCreators 期望得到的是一个 Object 作为 actionCreators 传进来
export default function bindActionCreators(actionCreators, dispatch) {
  // 如果只是传入一个action,则通过bindActionCreator返回被绑定到dispatch的函数
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  if (typeof actionCreators !== 'object' || actionCreators === null) {
  }

  const boundActionCreators = {} // 最终导出的就是这个对象
  for (const key in actionCreator) {
    const actionCreator = actionCreator[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

Кстати, здесь все должны помнить,Старайтесь не повторять название Действия, например 🌰

И у Сяо Пэна, и у А Куаня есть требование, то есть инициировать действие по изменению возраста.Первоначально эти два значения не имеют значения, а вода из колодца не делает речную воду, поэтому он написал этот код в коде.

// pengAction.js
export function changeAge(params, callback) {
  return {
    type: 'CHANGE_AGE',
    params,
    callback,
  }
}

// kuanAction.js
export function changeAge(params, callback) {
  return {
    type: 'CHANGE_AGE',
    params,
    callback,
  }
}

Вы сказали, что это совпадение, что продукт попросил Ahua сделать запрос.Когда нужно было нажать кнопку, возраст Сяо Пэна и А Куана изменился. Ава хочет использоватьbindActionCreatorsУстановите B, поэтому я написал этот код

const mapDispatchToProps => dispatch => {
  return {
    ...bindActionCreators(pengActions, dispatch);
    ...bindActionCreators(kuanActions, dispatch);
  }
}

Согласно нашемуbindActionCreatorsПонимание исходного кода, должно быть так 😯

pengActions = {
  changeAge: action,
}

export default function bindActionCreators(pengActions, dispatch) {
  // ...
  const boundActionCreators = {}

  for (const key in pengActions) {
    // key就是changeAge
    const actionCreator = pengActions[changeAge]
    // ...
    boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
  }
  return boundActionCreators
}

В итоге получается вот такой код

const mapDispatchToProps => dispatch => {
  return {
    changeAge, // ...bindActionCreators(pengActions, dispatch);
    changeAge // ...bindActionCreators(kuanActions, dispatch);
  }
}

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

const mapDispatchToProps => dispatch => {
  return {
    peng: {
      ...bindActionCreators(pengActions, dispatch);
    },
    kuan: {
      ...bindActionCreators(kuanActions, dispatch);
    }
  }
}

combineReducers

Как было сказано ранее, все приложениеstateхранятся в дереве состояний иесть только в одном магазине, тогда давайте посмотрим, насколько это свято~

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

типичныйжопа решает голову, так что в один прекрасный день, возможно, ангел предложил команду разработчиков редуксаissue, "К сожалению, вы можете предоставить API, который интегрирует все мои редукторы в одну часть, я хочу управлять состоянием по модульному принципу"

Например, пользовательский модуль называетсяuserReducer, товарный модуль, мы называемshopReducer, модуль заказа, мы называем егоorderReducer. Поскольку редукторов так много, как их объединить в один?

Таким образом, редукс обеспечиваетcombineReducersЭтот API кажется хорошим средством управления временем для обучения, видите ли, так многоreducer, можно интегрировать вместе, должно быть, потребовалось много усилий ~

Тогда давайте посмотрим, что делает combReducers~ Перед этим давайте посмотрим, как мы все используем эту штуку~

// 两个reducer
const pengReducer = (state = initPengState, action) => {}
const kuanReducer = (state = initKuanState, action) => {}

const appReducer = combineReducers({
  pengReducer,
  kuanReducer,
})
export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers) // 得到所有的reducer名

  // 1. 过滤reducers中不是function的键值对,过滤后符合的reducer放在finalReducers中
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

  // 2. 再一次过滤,判断reducer中传入的值是否合法
  let shapeAssertionError: Error
  try {
    // assertReducerShape 函数用于遍历finalReducers中的reducer,检查传入reducer的state是否合法
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }

  // 3. 返回一个函数
  return function combination(state, action) {
    // 严格redux又上线了,各种严格的检查
    // ...

    let hasChanged = false // 就是这逼,用来标志这个state是否有更新
    const nextState = {}

    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      // 这也就是为什么说combineReducers黑魔法--要求传入的Object参数中,reducer function的名称和要和state同名的原因
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]

      // 将reducer返回的值,存入nextState
      const nextStateForKey = reducer(previousStateForKey, action)
      nextState[key] = nextStateForKey

      // 如果任一state有更新则hasChanged为true
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    hasChanged =
      hasChanged || finalReducerKeys.length !== Object.keys(state).length
    return hasChanged ? nextState : state
  }
}

Этот исходный код на самом деле не слишком велик и не сложен, не так уж сложно вслед за А Куаном посмотреть на него вот так, верно? Итак, вот расширение вопроса, почему редукс должен возвращать новое состояние, а не может вернуть старое?

вернуть новое состояние

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

Давайте посмотрим, как мы пишем редукторы в целом

function pengReducer(state = initialState, action) {
  switch (action.type) {
    // 这种方式
    case 'CHANGE_AGE':
      return {
        ...state,
        age: action.data.age,
      }
    // 或者这种方式都行
    case 'ADD_AGE':
      return Object.assign({}, state, {
        age: action.data.age,
      })
  }
}

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

function pengReducer(state = initialState, action) {
  switch (action.type) {
    // 或者这种方式都行
    case 'CHANGE_AGE':
      state.age = action.data.age
      return state
  }
}

Когда мы запустим действие, вы спросите: БЛЯДЬ, почему страница не изменилась...

Вернемся к нашему исходному коду, мы можем видеть~

const nextStateForKey = reducer(previousStateForKey, action)

Здесь главное получить состояние после выполнения редьюсера, это не ключ, это состояние, а затем продолжить выполнение этой строчки кода~

hasChanged = hasChanged || nextStateForKey !== previousStateForKey

Чтобы сравнить, соответствуют ли старые и новые объекты, процесс浅比较法, поэтому, когда наш редьюсер напрямую возвращает объект старого состояния, Redux считает, что ничего не изменилось, в результате чего страница не обновляется.

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

Таким образом, redux принимает более «эвфемистическое» решение: когда есть какие-либо изменения, он должен возвращать новый объект, а когда изменений нет, возвращать старый объект~

applyMiddleware

Стоя на коленях, я чувствую, что в исходниках редукса самое сложное — это промежуточное ПО. Прежде чем говорить об этом, давайте поговорим о некоторых интересных вещах~

Когда дело доходит до реакции, я не знаю, каково первое впечатление каждого, но есть одно слово, которое, я думаю, должно быть известно большинству людей, а именно: 💗 функциональное программирование ~

функциональное программирование

  1. Функции являются гражданами первого класса

Как понять, в JS функции могут передаваться как переменные, или присваиваться переменной, и даже возвращаемый результат выполнения функции тоже может быть функцией.

const func = function () {}

// 1. 当作参数
function demo1(func) {}

// 2. 赋值给另一个变量
const copy_func = func

// 3. 函数执行的返回结果是函数
function demo2() {
  return func
}
  1. Данные неизменны

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

Может быть у кого-то из друзей будет эта библиотека:seamless-immutable, в редуксе подчеркивается, что значение состояния нельзя модифицировать напрямую (как было сказано выше, те, кто не слушает класс, выходят пукнуть), можно вернуть только новое состояние ~

Кроме того, следующие два предложения цитируются изDan Abramovв блоге:How Are Function Components Different from Classes?

В реакции мы начинаем сthis.props.xxxчитать данные в. Почему мы можем получить последний экземпляр? Дело не в том, что пропсы меняются, пропсы неизменны в React, они никогда не меняются. Тем не менее, это всегда будет изменчивым.

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

  1. Функция принимает только один параметр

Как вы это понимаете?Ребята вы наверное долго писали много параметров.Когда я это увидел,то обалдел.Но это правило.

Поэтому, когда вы посмотрите на код промежуточного программного обеспечения, вы не удивитесь, например, этой строке кода ~

const middleware = (store) => (next) => (action) => {}

В форме, которую мы можем понять, то есть:

const middleware = (store) => {
  return (next) => {
    return (action) => {}
  }
}

Некоторые люди здесь спрашивают: Нима, разве это не зависит только от трех параметров, можешь ли ты написать это так?

const middleware = (store, next, action) => {}

💐 только вы счастливы!Вы можете радоваться, но функциональное программирование обязательно, параметр может быть только один, это правило, понимаете?На моем месте можно только прикидываться трусом!

сочинять

Поговорим о комбинации compose, что это за штука, посмотрим на кусок кода:

const compose = (f, g) => {
  return (x) => {
    return f(g(x))
  }
}

const add = function (x) {
  return x + 2
}

const del = function (x) {
  return x - 1
}

// 使用组合函数,🧬 基因突变,强强联合
const composeFunction = compose(add, del)(100)

Угадайте, что, выполняя отпечатки composeFunction? Правильно, подарите себе аплодисменты 👏

Что ж, я научил вас самому мощному ниндзюцу: составить функцию в терминологии функционального программирования~

луковая модель

Вот еще один маленький друг, который запутался, зачем еще один пункт знаний? Не паникуйте, Ронг Акуан представит вам краткое введение.Мы говорили о функции составления выше, так какова же связь между функцией составления и моделью лука?

Модель лука — это, по сути, слой логики обработки, и в мире функционального программирования это означает использование функций в качестве единиц обработки. Не будем больше ничего говорить, давайте перейдем к первому 🌰 чтобы всем было понятно~

let middleware = []
middleware.push((next) => {
  console.log('A')
  next()
  console.log('A1')
})
middleware.push((next) => {
  console.log('B')
  next()
  console.log('B1')
})
middleware.push((next) => {
  console.log('C')
})

let func = compose(middleware)
func()

Угадайте, какой порядок печати? Правильно, результат печати: A -> B -> C -> B1 -> A1

Ой, неплохо, я чувствую себя немного. Когда программа запускается наnext()Когда текущая программа приостановлена, она перейдет к следующему промежуточному программному обеспечению и только после завершения обработки вернется к продолжению обработки.

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

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

Интерпретация исходного кода

Ну, не заставляйте, потому что мой查克拉Недостаточно, я не буду говорить о других требованиях ниндзюцу функционального программирования, хорошо, вот предосторожность, давайте посмотримapplyMiddlewareчто ты сделалбешеныйвещь~

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, ...args) => {
    const store = createStore(reducer, ...args)
    let dispatch: Dispatch = () => {}

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action, ...args) => dispatch(action, ...args),
    }
    const chain = middlewares.map((middleware) => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch,
    }
  }
}

Код очень короткий, давайте посмотрим, что происходит ~ Прежде всего, вернитеcreateStoreАнонимная функция в качестве параметра, затем эта функция возвращает другуюreducer, ...args (实际就是 initState, enhancer)Для анонимной функции параметра, а затем определить цепочку цепочки, это очень интересно.

const chain = middlewares.map((middleware) => middleware(middlewareAPI))

Сначала мы пропускаем входящийmiddlewaresпровестипилинг, и дайте промежуточное ПОmiddlewareопределяются намиmiddlewareAPIВводится как параметр, поэтому контекст каждого нашего промежуточного ПО — это диспетчеризация и getState, почему? Зачем вводить эти две вещи?

  • getState: Таким образом, каждый слой лука может получить текущее состояние.

  • диспетчеризация: чтобы действие могло быть передано следующему луку

хорошо, после того, как это выполнено, цепочка на самом деле(next) => (action) => { ... }Массив функций, то есть массив функций, возвращаемых промежуточным ПО после удаления. После этого используемstore.dispatchВвести как параметр ~ передатьcomposeправильномассив промежуточного ПОИсключенные функции более высокого порядка объединяются в цепочку вызовов. Вызванный один раз, все функции внутри промежуточного программного обеспечения будут выполнены.

// 或许换成这种形式,你更加能明白~
function compose(...chain) {
  return store.dispatch => {
    // ...
  }
}

сочинять в редуксе

Как упоминалось выше, это то, что привело нас кchainСформируйте цепочку вызовов, тогда мы увидим, как она это делает~

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return (arg) => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

Вы все еще помните комбинацию, которой вас научили выше, давайте попробуем восстановить внешний вид, понятный обычным людям~

(a, b) => (...args) => a(b(...args))

// 常人能看得懂的
(a, b) => {
  return (...args) {
    return a(b(...args))
  }
}

Два слова, воловья кожа 🐂🍺 Я должен вздохнуть, это действительно большой парень. Итак, ниже, давайте шаг за шагом рассмотрим, что это такое.

  • Подкинуть первый вопрос? быстрый ответ,dispatch 是用来干嘛的?

🙋 我会我会,dispatch 是用来分发 action 的, хорошо, тогда мы можем получить первую функцию

(store.dispatch) => (action) => {}

Проблема возникает снова, наш compose получает группу функций с одинаковой структурой после операции slap и, наконец, объединяет их в одну функцию.

  • Вторая проблема брошена здесь, которая должна пройтиdispatch, опять пройтиaction, Так что же нам делать? Используйте функции высшего порядка
middleware = (store.dispatch, store.getState) => (next) => (action) => {}

хорошо, тогда некоторым людям любопытно, что это за следующая вещь? фактическиСледующим, переданным промежуточному программному обеспечению, является store.dispatch.опять появилась странная проблема

  • Возвращаясь к третьему вопросу, как мы можем позволить каждому промежуточному программному обеспечению удерживать окончательную отправку?

Разработчики Redux используют характеристики замыканий, чтобы жестко привязать внутреннюю диспетчеризацию к внешней, MD, 🐂🍺

// 实例demo
let dispatch = () => {}

middlewares.map((middleware) =>
  middleware({
    getState,
    dispatch() {
      return dispatch
    },
  })
)

Таким образом, вы должны быть в состоянии понять истинное значение этого кода в исходном коде, верно?

//真实源码
let middlewareAPI = {
  getState: store.getState,
  dispatch: (action, ...args) => dispatch(action, ...args),
}

// 其实你把 middlewareAPI 写到 middleware 里边,就等价于上边那玩意了
const chain = middlewares.map((middleware) => middleware(middlewareAPI))

Тогда что нам нужно делать дальше? Важная вещь сказана трижды. Я сказал две стороны выше, и я сказал одну сторону здесь. То, что compose получает после обработки, является функцией, так как же должна быть вызвана эта функция? входящийstore.dispatchВсе в порядке~

// 真实源码
dispatch = compose(...chain)(store.dispatch)

Этот код фактически эквивалентен:

dispatch = chain1(chain2(chain3(store.dispatch)))

chain1, chain2 и chain3 — это элементы в цепочке, которые каррируются один раз и являются стабильными. Какую роль здесь играет диспетчеризация?

  • Связывание следующего каждого промежуточного программного обеспечения, говоря, что следующим на самом деле является store.dispatch
  • Предоставьте интерфейс для получения действий

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

какого хрена! Тем не менее, у меня все еще есть сомнения, я надеюсь, что старшие братья, которые увидят это, смогут развеять сомнения~

Мои сомнения: почему отправка не написана напрямую как store.dispatch в middlewareAPI, а является ссылкой на закрытие анонимной функции?

// 为什么不这么写....
let middlewareAPI = {
  getState: store.getState,
  dispatch: (action) => store.dispatch(action),
}

конец

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

Я писал эту статью в течение пяти дней, и она включает в себя немного больше очков знаний.Можно сказать, что некоторые очки знаний используются сейчас, но проблема невелика, потому что расширенные очки знаний не являются предметом этой статьи~ Написав эту статью, можно сказать, что она углубила мое понимание редукции. Не знаю, есть ли такие друзья, как я, которые хотят посмотреть исходники, но фронт в самый раз, но он просто не работает, поэтому я пошел на поле боя и решил прочитать интерпретацию какого-то блога статьи, но это было слишком сложно.Может я не понял, что хотел выразить автор? И ради некоторых из этих точек знаний блогеры проходят мимо. Это заставляет меня не знать, что такое upstream и downstream, смотреть на это прямо, а потом сразу же забывать.

Итак, в этой колонке [КТ] я хочу поделиться с вами проблемами, с которыми я столкнулся, и ямами, на которые я наступил на пути к обучению.Конечно, мое понимание не обязательно правильное, и если есть недопонимание, мы можем общаться вместе. Аолиги, давай не будем об этом, я собираюсь сделать демо, с нетерпением жду следующего~

Не спрашивайте, у меня тоже есть свой официальный аккаунт, но я не буду его выкладывать в открытый доступ. Официальный аккаунт был открыт в 2017. Сначала я хотел написать несколько историй о своих друзьях (больше не мейнстримных) а потом потому что статьи из моего блога были украдены, на некоторых публичных аккаунтах, чтобы сохранить оригинальность, знаете ли~

Ссылки по теме

Кстати, перепечатывать без согласия оригинального блоггера запрещено, иначе я увижу одного и сообщу о нем, это немного сложно~~