Почему vuex не рекомендует изменять состояние в действии

внешний интерфейс Vuex
Почему vuex не рекомендует изменять состояние в действии

задний план

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

Давайте вместе прочитаем исходный код~

1. Во-первых, мы можем найти следующий код в классе Store файла src/store.js

// ...
this.dispatch = function boundDispatch (type, payload) {
  return dispatch.call(store, type, payload)
}
this.commit = function boundCommit (type, payload, options) {
  return commit.call(store, type, payload, options)
}
// ...

Выше приведены два основных API Vuex: диспетчеризация и фиксация, которые используются для отправки действия и изменения соответственно. Итак, поскольку наша цель сегодня — «понять, почему состояние не может быть изменено в действии», давайте сначала посмотрим, как мутация изменяет состояние, но мутация передается через фиксацию, поэтому давайте посмотрим на внутреннюю реализацию фиксации.

commit&mutation

2. Основной код метода фиксации примерно такой:

commit (_type, _payload, _options) {
    // ...
    this._withCommit(() => {
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })
    // ...
}

Нетрудно заметить, что когда Vuex фиксирует определенный тип мутации, он сначала оборачивает эти мутации с помощью _withCommit, то есть передает _withCommit в качестве параметра; затем давайте посмотрим на внутреннюю реализацию _withCommit (ps: здесь Итак, «определенный тип мутации» связан с тем, что Vuex поддерживает объявление нескольких мутаций с одним и тем же именем, но только если они находятся в разных пространствах имен; то же самое верно для действий)

3. Код метода _withCommit выглядит следующим образом:

_withCommit (fn) {
    const committing = this._committing
    this._committing = true
    fn()
    this._committing = committing
  }

Да, вы правильно прочитали, на самом деле это всего 4 строки кода; здесь мы замечаем, что есть флаг _committing, этот флаг будет установлен в значение true перед выполнением fn, давайте сначала отметим этот момент, позже мы увидим его использование

4. Далее, что я хочу вам представить, это функция resetStoreVM, Ее функция заключается в инициализации хранилища. Она выполняется в первый раз в конструкторе Магазина.

function resetStoreVM (store, state, hot) {
  // ...
  if (store.strict) {
    enableStrictMode(store)
  }
// ...
}

Здесь нужно обратить внимание на одну вещь: resetStoreVM выносит суждение о strict (включен ли строгий режим). Предположим, мы включаем строгий режим, тогда будет выполняться функция enableStrictMode. Продолжим смотреть на ее внутреннюю реализацию.

function enableStrictMode (store) {
  store._vm.$watch(function () { return this._data.?state }, () => {
    if (process.env.NODE_ENV !== 'production') {
      assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)
    }
  }, { deep: true, sync: true })
}

Здесь отслеживается состояние экземпляра компонента Vue. После отслеживания изменения актив (утверждение) будет выполнен. Утверждение является флагом _committing, о котором я только что сказал всем помнить, поэтому давайте посмотрим на этот актив. , что сделал

5. Метод актива находится в файле src/util.js

export function assert (condition, msg) {
  if (!condition) throw new Error(`[vuex] ${msg}`)
}

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

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

dispatch&action

6. Код отправки примерно такой:

dispatch (_type, _payload) {
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)

    const action = { type, payload }
    const entry = this._actions[type]

  // ...
    const result = entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)
  // ...
  }

Здесь мы отмечаем, что когда у определенного типа действия есть только одно объявление, обратный вызов действия будет выполняться как обычная функция, а когда есть несколько объявлений, они обрабатываются как экземпляры Promise и выполняются с Promise.all , как мы все знаем. , Promise.all не гарантирует порядок при выполнении промиса, то есть если есть 3 экземпляра промиса: P1, P2, P3, какой из них не обязательно возвращает результат первым, то мы хорошенько подумаем: какой происходит, если одно и то же состояние изменяется в нескольких действиях одновременно?

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

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

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

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

function enableStrictMode (store) {
  store._vm.$watch(function () { return this._data.?state }, () => {
    if (process.env.NODE_ENV !== 'production') {
      assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)
    }
  }, { deep: true, sync: true })
}

То есть, если вы принудительно измените состояние действием в производственной среде, Vuex не остановит вас, он может просто выдать вам предупреждение; и логически, если мы можем гарантировать, что существует только одно объявление для одного и того же типа действие, поэтому независимо от того, используете ли вы действие или мутацию для изменения состояния, результат будет одинаковым, потому что Vuex не использует Promise.all для выполнения действия в этом случае, поэтому не будет проблем с последовательным возвратом результатов.

dispatch (_type, _payload) {
    // ...
    const result = entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)
    // ...
  }

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

заключительные замечания

Это ограничение Vuex на самом деле связано с соображениями дизайна кода Действия и изменения имеют свои собственные функции и по существу соблюдают принцип «единой ответственности». Выше мой анализ проблемы "почему Vuex не позволяет модифицировать состояние в действии", надеюсь будет всем полезен, и исправления приветствуются

Подписывайтесь на нас

关注公众号前端论道