Redux на самом деле очень прост (принцип)

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

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

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

Основной API

Этот набор кода представляет собой набор кода, написанный автором после прочтения исходного кода Redux и понимания его дизайнерских идей.Дизайн API следует тому же принципу, что и оригинал, за исключением некоторых ненужных API.

createStore

Этот метод является ядром ядра Redux, он соединяет все остальные функции вместе и предоставляет API операции для вызова разработчиком.

const INIT = '@@redux/INIT_' + Math.random().toString(36).substring(7)

export default function createStore (reducer, initialState, enhancer) {
  if (typeof initialState === 'function') {
    enhancer = initialState
    initialState = undefined
  }

  let state = initialState
  const listeners = []
  const store = {
    getState () {
      return state
    },
    dispatch (action) {
      if (action && action.type) {
        state = reducer(state, action)
        listeners.forEach(listener => listener())
      }
    },
    subscribe (listener) {
      if (typeof listener === 'function') {
        listeners.push(listener)
      }
    }
  }

  if (typeof initialState === 'undefined') {
    store.dispatch({ type: INIT })
  }

  if (typeof enhancer === 'function') {
    return enhancer(store)
  }

  return store
}

Во время инициализации createStore будет активно запускать отправку.Его action.type — это встроенный INIT системы, поэтому он не будет совпадать с каким-либо определенным разработчиком action.type в редюсере.Он следует логике по умолчанию в switch.Цель это получить инициализированное состояние.

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

Третий усилитель параметра используется только при использовании промежуточного программного обеспечения.Обычно мы используем его с applyMiddleware.Он может улучшить функцию отправки, например, обычно используемые регистратор и преобразователь, которые улучшают функцию отправки.

В то же время createStore вернет некоторые API-интерфейсы операций, в том числе:

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

applyMiddleware

Этот метод расширяет функциональные возможности диспетчеризации через промежуточное ПО.

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

состав функций

Если значение должно пройти через несколько функций, чтобы стать другим значением, вы можете объединить все промежуточные шаги в одну функцию, которая называется композицией функций (compose).

Например

function add (a) {
  return function (b) {
    return a + b
  }
}

// 得到合成后的方法
let add6 = compose(add(1), add(2), add(3))

add6(10) // 16

Ниже мы используем очень хитрый способ написания функции compose (композиции).

export 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)))
}

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

Благодаря этому основанию applyMiddleware становится очень простым.

import { compose } from './utils'

export default function applyMiddleware (...middlewares) {
  return store => {
    const chains = middlewares.map(middleware => middleware(store))
    store.dispatch = compose(...chains)(store.dispatch)

    return store
  }
}

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

function middleware (store) {
  return function f1 (dispatch) {
    return function f2 (action) {
      // do something
      dispatch(action)
      // do something
    }
  }
}

Как можно заметить,chainsПредставляет собой массив функций f1.Искомую f1 объединяем в одну функцию через compose, которая пока называется F1.Затем передаем в F1 исходную диспетчеризацию.После преобразования функции f2 слой за слоем получаем новую , метод отправки, этот процесс такой же, как модель промежуточного программного обеспечения Koa (модель лука).

Чтобы облегчить ваше понимание, давайте возьмем еще один пример, есть следующие два промежуточных программного обеспечения

function middleware1 (store) {
  return function f1 (dispatch) {
    return function f2 (action) {
      console.log(1)
      dispatch(action)
      console.log(1)
    }
  }
}

function middleware2 (store) {
  return function f1 (dispatch) {
    return function f2 (action) {
      console.log(2)
      dispatch(action)
      console.log(2)
    }
  }
}

// applyMiddleware(middleware1, middleware2)

Угадайте, каков порядок приведенного выше вывода журнала?

Что ж, ответ раскрыт: 1, 2, (исходная рассылка), 2, 1.

Почему это так?因为middleware2接收的dispatch是最原始的,而middleware1接收的dispatch是经过middleware1改造后的,我把它们写成如下的样子,大家应该就清楚了。

console.log(1)

/* middleware1返回给middleware2的dispatch */
console.log(2)
dispatch(action)
console.log(2)
/* end */

console.log(1)

То же самое верно для трех или более промежуточных программ.

До сих пор было объяснено самое сложное и трудное для понимания промежуточное ПО.

combineReducers

Поскольку Redux — это режим управления потоком с одним состоянием, если есть несколько редьюсеров, нам нужно их объединить.Логика этого блока относительно проста, и код применяется напрямую.

export default function combineReducers (reducers) {
  const availableKeys = []
  const availableReducers = {}

  Object.keys(reducers).forEach(key => {
    if (typeof reducers[key] === 'function') {
      availableKeys.push(key)
      availableReducers[key] = reducers[key]
    }
  })

  return (state = {}, action) => {
    const nextState = {}
    let hasChanged = false

    availableKeys.forEach(key => {
      nextState[key] = availableReducers[key](state[key], action)

      if (!hasChanged) {
        hasChanged = state[key] !== nextState[key]
      }
    })

    return hasChanged ? nextState : state
  }
}

combReucers запихивает один редюсер в объект, каждому редьюсеру соответствует уникальное значение ключа, при изменении состояния одного редуктора значение соответствующего значения ключа также меняется, а затем возвращает все состояние.

bindActionCreators

Этот метод должен связать наше действие и отправку.

function bindActionCreator (actionCreator, dispatch) {
  return function () {
    dispatch(actionCreator.apply(this, arguments))
  }
}

export default function bindActionCreators (actionCreators, dispatch) {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  const boundActionCreators = {}

  Object.keys(actionCreators).forEach(key => {
    let actionCreator = actionCreators[key]

    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  })

  return boundActionCreators
}

Он возвращает набор методов, которые можно вызывать напрямую для запуска диспетчеризации.

промежуточное ПО

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

logger

function getFormatTime () {
  const date = new Date()
  return date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds() + ' ' + date.getMilliseconds()
}

export default function logger ({ getState }) {
  return next => action => {
    /* eslint-disable no-console */
    console.group(`%caction %c${action.type} %c${getFormatTime()}`, 'color: gray; font-weight: lighter;', 'inherit', 'color: gray; font-weight: lighter;')
    // console.time('time')
    console.log(`%cprev state`, 'color: #9E9E9E; font-weight: bold;', getState())
    console.log(`%caction    `, 'color: #03A9F4; font-weight: bold;', action)

    next(action)

    console.log(`%cnext state`, 'color: #4CAF50; font-weight: bold;', getState())
    // console.timeEnd('time')
    console.groupEnd()
  }
}

thunk

export default function thunk ({ getState }) {
  return next => action => {
    if (typeof action === 'function') {
      action(next, getState)
    } else {
      next(action)
    }
  }
}

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

опыт

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

Но на самом деле у автора есть другая проблема: каждая отправка будет повторно отображать все представление.Хотя React выполняет diff для виртуального DOM, а затем направленно отображает реальный DOM, который необходимо обновить, мы знаем, что обычно сценарии, в которых используется Redux являются средними и крупными приложениями. , для управления огромными данными о состоянии. В настоящее время сравнение всего виртуального DOM может привести к относительно очевидной потере производительности (процесс сравнения фактически сравнивает объект и поля объекта одно за другим , Если данные достигают определенного порядка величины, хотя реальный DOM не обрабатывается, также может быть значительное снижение производительности, в небольших приложениях снижение производительности diff незначительно из-за меньшего количества данных).

Адрес исходного кода этой статьи:GitHub.com/Энсон Хуанг/…