Исходный код Vuex: углубитесь в пространство имен vuex

внешний интерфейс исходный код JavaScript Vuex

предисловие

После прочтения модуля я чувствую, что у меня естьnamespacedМодуль — это настоящий модуль, поэтому я добавил эту статью.

Пронередность имен делает Getter, мутации и действия по-настоящему модульными, что позволяет хранить мутации, специфичные модуля и т. Д. В этой статье давайте посмотрим, как реализован намен?

Подготовить

На этот раз я читаю vuex2.1.0Версия, ткните пожалуйста в исходникздесь. Рекомендуется скачать и открыть код, чтобы следовать расшифровке, иначе вы можете выглядеть растерянным.

интерпретировать

Код Vuex 2.1.0 примерно похож на 2.0.0, за исключением того, что модуль извлекается в каталог. В нем есть два файла, Module-Collection.js и module.js, и интерпретация этих двух файлов будет включена в следующую.

Или начните с конструктора хранилища и начните искать код, связанный с модулем и пространством имен.

constructor (options = {}) {
  this._modules = new ModuleCollection(options)
  this._modulesNamespaceMap = Object.create(null)

  // init root module.
  // this also recursively registers all sub-modules
  // and collects all module getters inside this._wrappedGetters
  installModule(this, state, [], this._modules.root)
}

Давайте положим_modulesNamespaceMapВынесенное отдельно, это свойство в основном используется вспомогательной функцией mapState и используется при интерпретации mapState.

Таким образом, конструктор делает с модулем две вещи: первая — инициализировать _modules, передавая опции параметра конфигурации, а вторая — регистрировать модуль с помощью installModule.

Дел не так много, но кодов в ней реализовано довольно много, морально все должны быть готовы ха-ха.

ModuleCollection

Во-первых, давайте посмотрим, что делает new ModuleCollection для инициализации _modules. Таргетингmodule-collection.jsFile, увидел его конструктор:

constructor (rawRootModule) {
  // register root module (Vuex.Store options)
  this.root = new Module(rawRootModule, false)

  // register all nested modules
  if (rawRootModule.modules) {
    forEachValue(rawRootModule.modules, (rawModule, key) => {
      this.register([key], rawModule, false)
    })
  }
}

构造函数做了也是两件事情,一件是注册了一个根 module,另一个是遍历注册子 module。 Открытьmodule.jsВзгляните на основной используемый код:

export default class Module {
  constructor (rawModule, runtime) {
    this.runtime = runtime
    this._children = Object.create(null)
    this._rawModule = rawModule
  }

  addChild (key, module) {
    this._children[key] = module
  }

  getChild (key) {
    return this._children[key]
  }
}

Добавьте в конструктор _children, то есть подмодуль, а затем сохраните текущий модуль в _rawModule. Затем будут использоваться два метода addChild и getChild, Как следует из названия, они используются для добавления подмодулей и получения подмодулей.

Вернитесь ко второму шагу конструктора ModuleCollection и найдитеregisterметод:

// 得到对应 path 的 module
get (path) {
  return path.reduce((module, key) => {
    return module.getChild(key)
  }, this.root)
}
  
register (path, rawModule, runtime = true) {
  // path.slice(0, -1) 表示去掉最后一个
  // 取得父 module
  const parent = this.get(path.slice(0, -1))

  // new 一个新的 module
  const newModule = new Module(rawModule, runtime)

  // 添加子 module
  parent.addChild(path[path.length - 1], newModule)

  // register nested modules
  if (rawModule.modules) {
    // 递归注册子 module
    forEachValue(rawModule.modules, (rawChildModule, key) => {
      this.register(path.concat(key), rawChildModule, runtime)
    })
  }
}

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

installModule

После инициализации свойства магазина _Modules следующий шаг - модуль регистрации. Методы нацеливания MODELModule:

function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length
  const namespace = store._modules.getNamespace(path)

  // register in namespace map
  if (namespace) {
    store._modulesNamespaceMap[namespace] = module
  }

  // set state
  if (!isRoot && !hot) {
    const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    store._withCommit(() => {
      Vue.set(parentState, moduleName, module.state)
    })
  }

  const local = module.context = makeLocalContext(store, namespace)

  module.forEachMutation((mutation, key) => {
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, path)
  })

  module.forEachAction((action, key) => {
    const namespacedType = namespace + key
    registerAction(store, namespacedType, action, local, path)
  })

  module.forEachGetter((getter, key) => {
    const namespacedType = namespace + key
    registerGetter(store, namespacedType, getter, local, path)
  })

  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })
}

getNamespace

getNamespace (path) {
  let module = this.root
  return path.reduce((namespace, key) => {
    module = module.getChild(key)
    return namespace + (module.namespaced ? key + '/' : '')
  }, '')
}

// options
{
  modules: {
    a: {
      modules: {
        b: {
          // ...
        }
      }
    }
  }
}

Пространства имен, соответствующие массиву путей:

// []
/

// [a]
/a/

// [a, b]
/a/b/

registerAction

После получения пространства имен следующим шагом будет регистрация действия.

const local = module.context = makeLocalContext(store, namespace)

module.forEachAction((action, key) => {
  const namespacedType = namespace + key
  registerAction(store, namespacedType, action, local, path)
})

Он был интерпретирован до второго шага registerAction, не более чем подмодуль в действии как ключ, представленный namespacedType, используемый для различения хранилища ключей._actions. Так что первый шаг в интерпретации этого кода в основном makeLocalContext.

function makeLocalContext (store, namespace) {
  const noNamespace = namespace === ''

  const local = {
    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args

      if (!options || !options.root) {
        type = namespace + type
        if (!store._actions[type]) {
          console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
          return
        }
      }

      return store.dispatch(type, payload)
    }
  }

  return local
}

После упрощения кода осталось еще много. Метод unifyObjectStyle предназначен только для поддержки нескольких форматов передачи параметров и не будет здесь подробно объясняться.

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

modules: {
  foo: {
    namespaced: true,

    actions: {
      // 在这个模块中, dispatch 和 commit 也被局部化了
      // 他们可以接受 `root` 属性以访问根 dispatch 或 commit
      someAction ({ dispatch, commit, getters, rootGetters }) {
        dispatch('someOtherAction') // -> 'foo/someOtherAction'
        dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
      }
    }
  }
}

helper

Вспомогательные функции vuex имеют функции привязки с пространствами имен, как показано ниже:

computed: {
  ...mapState('some/nested/module', {
    a: state => state.a,
    b: state => state.b
  })
},
methods: {
  ...mapActions('some/nested/module', [
    'foo',
    'bar'
  ])
}

С точки зрения mapActions, он в основном оборачивает слой normalizeNamespace в исходную реализацию. Откройте файл helper.js и найдите метод normalizeNamespace:

function normalizeNamespace (fn) {
  return (namespace, map) => {
    if (typeof namespace !== 'string') {
      map = namespace
      namespace = ''
    } else if (namespace.charAt(namespace.length - 1) !== '/') {
      namespace += '/'
    }
    return fn(namespace, map)
  }
}

Главное, соединить путь к вызову store.__actions.

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

Конкретного кода реализации _modulesNamespaceMap не так много, поэтому я не буду его подробно интерпретировать.

Суммировать

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

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

Пространство имен используется как путь и как ключ массива для доступа к действиям подмодуля и т.д. Таким образом, действия в подмодулях могут быть доступны отдельно. По сравнению с предыдущей версией 2.0.0, которая могла обращаться только к состоянию в подмодуле, но не к действию и т. д., пространство имен в версии 2.1.0 было добавлено, чтобы сделать его более модульным.