Повторный вызов storeAPI.disapatch (действие) внутри промежуточного программного обеспечения вызывает «слишком много рекурсии»

#javascript #reactjs #redux #react-redux

#javascript #reactjs #redux #реагировать-redux

Вопрос:

Я работаю над учебником по основам Redux. В разделе «Написание пользовательского промежуточного программного обеспечения» мы узнаем, что промежуточное программное обеспечение записывается как серия из трех вложенных функций, например:

 // Outer function:
function exampleMiddleware(storeAPI) {
  return function wrapDispatch(next) {
    return function handleAction(action) {
      // Do anything here: pass the action onwards with next(action),
      // or restart the pipeline with storeAPI.dispatch(action)
      // Can also use storeAPI.getState() here

      return next(action)
    }
  }
}
 

exampleMiddleware объясняется следующим образом:

exampleMiddleware: внешняя функция на самом деле является самим «промежуточным программным обеспечением». Он будет вызван applyMiddleware и получит объект storeAPI, содержащий функции хранилища {dispatch, getState} . Это те же функции отправки и getState, которые фактически являются частью хранилища. Если вы вызовете эту функцию отправки, она отправит действие в начало конвейера промежуточного программного обеспечения.Это вызывается только один раз.

Я не понял, что подразумевалось под вторым последним предложением ( If you call this dispatch function, it will send the action to the start of the middleware pipeline ), поэтому я попытался вызвать store.dispatch(action) одно из промежуточных программных средств, предоставленных в src/exampleAddons/middleware.js примере приложения, чтобы посмотреть, что происходит, и получил «слишком много рекурсии». Вот демонстрация.

Таким storeAPI.dispatch() образом, составная функция отправки всех промежуточных программ объединена, а не исходного хранилища dispatch , что объясняет рекурсию. Но тогда какая польза storeAPI.dispatch() ? Я неправильно его использую?

В applyMiddleware исходном коде:

 function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    // ...1) createStore is called and the resulting store is saved as `store`
    const store = createStore(...args)
    
    // ...2) a `dispatch` variable is defined and assigned some function
    let dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. '  
          'Other middleware would not be applied to this dispatch.'
      )
    }
    
    // ...3) a middlewareAPI object is defined containing the store's getState method and the `dispatch` function from 2).
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    
    // ...4) the middlewares passed to applyMiddleware are called with the `middlewareAPI` object from 3) and the resulting functions are saved in array `chain`.
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    
    // ...5) the middlewares are composed and the resulting "composed" middleware function is called with `store.dispatch`. 
    // This returns a composed dispatch function that chains together the `handleAction` functions of all the middlewares passed to applyMiddleware. 
    // This composed dispatch gets assigned to the `dispatch` variable from 2). 
    // Since the `storeAPI.dispatch` is referencing this variable, calling `storeAPI.dispatch` now calls the composed middleware function, which causes the infinite loop. 
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}
 

Похоже, что бесконечный цикл является результатом повторного назначения на шаге 5 в аннотациях выше. Но я не уверен, верны ли мои рассуждения или я даже storeAPI.dispatch правильно использую. Я был бы признателен за любые рекомендации, которые сообщество могло бы предоставить здесь, поскольку я не смог найти никаких примеров промежуточного программного обеспечения, которые вызывают storeAPI.dispatch() .

Комментарии:

1. Это обсуждение проливает некоторый свет на использование. storeAPI.dispatch кажется, полезно для условной отправки действия в промежуточном программном обеспечении, например if (condition) { storeAPI.dispatch(someOtherAction) } else { next(action) } . — если условие истинно, то someOtherAction будет передано через конвейер промежуточного программного обеспечения, в противном случае текущее действие будет передано следующему промежуточному программному обеспечению. Вы можете увидеть этот шаблон здесь .

2. Было бы полезно иметь пример этого в документах. Я понял, что storeAPI.dispatch это «перезапускает» цепочку промежуточного программного обеспечения, но не был уверен, почему это полезная функция, пока не увидел ссылки в моем предыдущем комментарии.

3. storeAPI.dispatch (действие) снова отправит действие, поэтому redux запустит промежуточные программы, которые будут вызывать storeAPI.dispatch(action) снова, поэтому redux запустит промежуточные программы, которые будут вызывать storeAPI.dispatch(action) снова, это ваш бесконечный цикл. Это похоже на написание рекурсивной функции, которая никогда не перестает повторяться: const recur = (arg)=>recur(arg)

4. Если вы посмотрите на реализацию thunk, вы увидите, что она не будет вызывать next, если действие является функцией: const thunkMiddleware = ({getState,dispatch}) => next => action => typeof action === 'function' ? action(dispatch,getState) : next(action)

Ответ №1:

Да, вызов storeAPI.dispatch() промежуточного программного обеспечения отправляет действие в самое начало конвейера промежуточного программного обеспечения. Это означает, что если у нас есть промежуточные a->b->c->store программы и b вызовы storeAPI.dispatch({type: "some/action"}) , промежуточное b программное обеспечение увидит, что тот же самый объект действия проходит почти сразу.

Из-за этого промежуточное программное обеспечение никогда не должно вызывать storeAPI.dispatch() безоговорочно, потому что это приведет к бесконечным циклам!Это в основном та же проблема, что и что-то вроде setState() безусловного вызова в useEffect перехватчике компонента React. Эффект запускается после рендеринга и setState() ставит в очередь другой рендеринг, поэтому, если вы всегда устанавливаете состояние каждый раз, вы всегда принудительно выполняете повторный рендеринг, и это бесконечный цикл. Здесь то же самое.

Таким образом, любое использование storeAPI.dispatch() в промежуточном программном обеспечении должно быть обернуто в условную проверку, чтобы это происходило только некоторое время, а не все время.

Комментарии:

1. Спасибо @markerikson, мне не приходило в голову, что его следует использовать условно. Именно так оно и используется здесь .

Ответ №2:

Но тогда какая польза от storeAPI.dispatch()?

Давайте рассмотрим наивную реализацию промежуточного программного обеспечения thunk. Промежуточное программное обеспечение в основном позволяет dispatch получать функцию (называемую функцией отправки) в качестве своего аргумента (который обычно является простым объектом действия).

 const asyncFunctionMiddleware = store => next => action => {
    if (typeof action === 'function') {
        return action(store.dispatch, store.getState); // (*)
    }
    return next(action);
}
 

Асинхронная логика войдет в функцию отправки, которая была снабжена двумя существенными аргументами: dispatch и getState — См. Строку (*) .

Этих двух аргументов достаточно, чтобы разработчики могли взаимодействовать с хранилищем redux в своей асинхронной логике. И для предполагаемого варианта использования воля разработчика состоит в том, чтобы иметь возможность отправлять действие как обычную операцию (со всеми предоставленными функциями промежуточного программного обеспечения). Поэтому store.dispatch здесь в реализации используется a .

Что произойдет, если мы передадим next вместо этого?В зависимости от положения промежуточного программного обеспечения thunk в списке промежуточного программного обеспечения, next следующим завершенным объектом отправки, за которым следует промежуточное программное обеспечение thunk. Представьте, что порядок такой a->thunk->b->store . Тогда отправка с использованием next в thunk не окажет никакого влияния промежуточного a программного обеспечения на действие. Такое поведение нежелательно в случае использования thunk, поэтому использование store.dispatch здесь уместно.

Поэтому thunk — это ситуация, которую мы должны использовать store.dispatch в промежуточном программном обеспечении