current position:Home>[source code] vuex

[source code] vuex

2021-08-25 09:34:37 woow_ wu7

Navigation

[[ thorough 01] Execution context ](https://juejin.im/post/684490...)
[[ thorough 02] Prototype chain ](https://juejin.im/post/684490...)
[[ thorough 03] Inherit ](https://juejin.im/post/684490...)
[[ thorough 04] The event loop ](https://juejin.im/post/684490...)
[[ thorough 05] currying Partial function Function memory ](https://juejin.im/post/684490...)
[[ thorough 06] Implicit conversion and Operator ](https://juejin.im/post/684490...)
[[ thorough 07] Browser caching mechanism (http Caching mechanisms )](https://juejin.im/post/684490...)
[[ thorough 08] Front end security ](https://juejin.im/post/684490...)
[[ thorough 09] Depth copy ](https://juejin.im/post/684490...)
[[ thorough 10] Debounce Throttle](https://juejin.im/post/684490...)
[[ thorough 11] Front-end routing ](https://juejin.im/post/684490...)
[[ thorough 12] Front-end modularization ](https://juejin.im/post/684490...)
[[ thorough 13] Observer mode Publish subscribe mode Two way data binding ](https://juejin.im/post/684490...)
[[ thorough 14] canvas](https://juejin.im/post/684490...)
[[ thorough 15] webSocket](https://juejin.im/post/684490...)
[[ thorough 16] webpack](https://juejin.im/post/684490...)
[[ thorough 17] http and https](https://juejin.im/post/684490...)
[[ thorough 18] CSS-interview](https://juejin.im/post/684490...)
[[ thorough 19] Handwriting Promise](https://juejin.im/post/684490...)
[[ thorough 20] Handwritten functions ](https://juejin.im/post/684490...)

[[react] Hooks](https://juejin.im/post/684490...)

[[ Deploy 01] Nginx](https://juejin.im/post/684490...)
[[ Deploy 02] Docker Deploy vue project ](https://juejin.im/post/684490...)
[[ Deploy 03] gitlab-CI](https://juejin.im/post/684490...)

[[ Source code -webpack01- Pre knowledge ] AST Abstract syntax tree ](https://juejin.im/post/684490...)
[[ Source code -webpack02- Pre knowledge ] Tapable](https://juejin.im/post/684490...)
[[ Source code -webpack03] Handwriting webpack - compiler Simple compilation process ](https://juejin.im/post/684490...)
[[ Source code ] Redux React-Redux01](https://juejin.im/post/684490...)
[[ Source code ] axios ](https://juejin.im/post/684490...)
[[ Source code ] vuex ](https://juejin.im/post/684490...)
[[ Source code -vue01] data Response type and Initialize rendering ](https://juejin.im/post/684490...)
[[ Source code -vue02] computed Response type - initialization , visit , The update process ](https://juejin.im/post/684490...)
[[ Source code -vue03] watch Listening properties - Initialization and update ](https://juejin.im/post/684490...)
[[ Source code -vue04] Vue.set and vm.$set ](https://juejin.im/post/684490...)
[[ Source code -vue05] Vue.extend ](https://juejin.im/post/684490...)

[[ Source code -vue06] Vue.nextTick and vm.$nextTick ](https://juejin.im/post/684790...)

Pre knowledge

Some words

Mutation: variation 
raw: Unprocessed 
assert:  Assertion 

internal: Inside 
( store internal state : store Inside state )

duplicate: repeat , assignment , copy 
localized: Local 

unify: Unified 
( unifyObjectStyle  Unified object type  )

Valid:  Effective 

vue How to write a plug-in

  • Plug ins are usually used to vue add to ( Global capabilities )

    • The global method , Global resources , The whole thing is in , vue Example method etc.
    • namely Vue Constructor static methods and properties ,Vue.prototype Instance methods and properties ,mixin,directive etc.
  • If using plug-ins

    • By component form :

        1. Vue.use(plugin, [options])
        1. new Vue( Component options )
    • adopt script Mode introduction

      • such as vue-router plug-in unit

        • Will be in index.js Internal judgment If it is ( Browser environment and window.Vue There is ), Call it automatically window.Vue.use(VueRouter)
  • Be careful :

    • <font color=red> Each plug-in should expose a install Method , also install The first parameter of the method must be Vue Constructors , The second parameter is an optional parameter object </font>

      • because install The first parameter of the method is Vue Constructors , So you can get Vue Upper directive, mixin, wait
    • You'll know later if it's not exposed install Method , The plug-in itself must be a function , This function will be treated as install Method
  • Code

     plug-in unit :
    
    const firstPlugin = {
      install(Vue, options) {
          console.log(options, 'options')
    
          //  The global method  firstGlobalMethod
          Vue.firstGlobalMethod = function () {
              console.log(' plug-in unit  -  The global method  -  namely Vue Static method of constructor ')
          }
    
          //  Add global resources   Global instructions 
          Vue.directive('first-directive', {
              bind(el, binding, vnode, oldVnode) {
                  console.log(' Call it once , The first time an instruction is bound to an element . Here you can do one-time initialization settings ')
                  console.log(el, 'el  Element bound by instruction , Can be used for direct operation DOM')
                  console.log(binding, 'binding')
                  console.log(vnode, 'Vue  Compile generated virtual nodes ')
                  console.log(oldVnode, ' Previous virtual node , Only in  update  and  componentUpdated  Available in hook ')
              },
              inserted() {
                  console.log(' Called when the bound element is inserted into the parent node  ( Only the parent node is guaranteed to exist , But it doesn't have to be inserted into the document )')
              }
          })
    
          //  Global mix   Injection component options 
          Vue.mixin({
              created: function () {
                  console.log(' plug-in unit  -  Global mix  -  Mix in all components created() Life cycle ')
              }
          })
    
          //  Example method 
          //  The general rule is Vue.prototype The properties and methods mounted on the must be in the form of  $  start 
          Vue.prototype.$firstPluginMethod = function () {
              console.log(' plug-in unit  -  Example method  -  Mounted on vue Method on instance prototype object , Remember that the property and method are represented by  $  start ')
          }
      }
    }
    
    export default firstPlugin
     Register plug-ins :
    
    import Vue from 'vue'
    import App from './App.vue'
    import router from './router'
    import store from './store'
    import axios from 'axios'
    import firstFlugin from './plugin/first-plugin'
    
    Vue.config.productionTip = false
    
    Vue.prototype.$axios = axios
    
    Vue.use(firstFlugin) // --------------------------------------------------------------  Register plug-ins 
    
    new Vue({
    router,
    store,
    render: h => h(App)
    }).$mount('#app')
    

Vue.use()

  • Vue.use( plugin )
  • Parameters :object perhaps function
  • effect :

    • install Vue plug-in unit
    • Plug ins are objects : Need to provide install Method
    • Plug ins are functions : They will act as install Method
    • install Method : Will Vue As a parameter , stay Vue.use() Automatically execute on call
  • Be careful :

    • <font color=red>Vue.use() Need to be in new Vue() Called before </font>
    • When install Method is called multiple times by the same plug-in , The plug-in will only be installed once
  • Vue.use() Source code

    • I have mainly done the following things

      1. If the plug-in has been registered

        • Go straight back to ( return this Mainly to chain call )
      2. Not registered

        • Determine whether the plug-in is an object or a function

          • <font color=red> object : call install Method </font>
          • <font color=red> function : Use the plug-in as a install Method , Call this function directly </font>
        • Add the plug-in to the plug-in array , Judge whether the registration is outdated next time , Just registered
        • return this Convenient chain call
      3. summary :

        • Vue.use() The most important thing is to call the plug-in install Method
    // Vue.use()  Source code 
    //  File path :core/global-api/use.js
    
    
    import { toArray } from '../util/index'
    
    export function initUse(Vue: GlobalAPI) {
    Vue.use = function (plugin: Function | Object) {
      // Vue.use  stay Vue Mount on constructor use Method 
    
      const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
      // installedPlugins
      //  If  Vue._installedPlugins  There is a direct assignment 
      //  If  Vue._installedPlugins  There is no empty array assigned 
    
      if (installedPlugins.indexOf(plugin) > -1) {
        //  The plug-in is in the plug-in array installedPlugins If it exists in, it returns directly 
        //  Return here  this, You can realize chain call , there this Namely Vue
        return this
      }
    
      const args = toArray(arguments, 1)
      // additional parameters
      //  Will remove Vue Other parameters are integrated into the array 
    
      args.unshift(this)
      //  take Vue Add to the front of the array 
      //  because args To be a plug-in install Method parameters , and install Method specifies that the first argument must be Vue Constructors  
    
      if (typeof plugin.install === 'function') {
        //  Plug ins are objects , Just put the plug-in object install Methodical this Bind to this plugin On , Pass in parameters to execute 
        plugin.install.apply(plugin, args)
      } else if (typeof plugin === 'function') {
        //  Plug ins are functions , Reference execution 
        plugin.apply(null, args)
      }
    
      installedPlugins.push(plugin)
      //  If the plug-in is in installedPlugins non-existent , First assign an empty array to it , perform isntall after , Add the plug-in to installedPlugins Indicates presence 
    
      return this
      // return this  For chained calls ,this Refer to  Vue
    }
    }
    
    
    
    
    // ------------------------------------------------------
    // toArray(list, start)
    //  such as :
    // list = [1,2,3,4] 
    // start = 1
    export function toArray(list: any, start?: number): Array<any> {
    start = start || 0
    let i = list.length - start
    const ret: Array<any> = new Array(i)
    while (i--) {
      ret[i] = list[i + start]
      // i--  Assign value first, i.e  while(3)
      //  Then subtract 1 namely i=2
      // ret[2] = list[3]
      // ret[1] = list[2]
      // ret[0] = list[1]
      // ret = [2,3,4]
    }
    return ret
    }
    

vuex Source code

(1) vuex How to install and use

  • install

    • npm install vuex -S
  • Use

    main.js in 
    ----
    
    import Vuex from 'vuex'
    Vue.use(Vuex) // --------------------------------  Would call Vuex Upper install Method 
    const store = new Vuex.Store({ // --------------- new Store(options)
    state: {},
    mutations: {},
    actions: {},
    getters: {},
    modules: {}
    })
    const app = new Vue({
    router,
    store, // --------------------------------------  Inject store
    render: h => h(App)
    }).$mount('#app')
    

(2) Vue.use(Vuex)

  • Vue.use(Vuex) Would call Vuex Medium Vuex.install Method

    • <font color=red>Vuex.install</font> Method will call <font color=red>applyMixin(Vue)</font>

      • applyMixin(Vue)

        • The main thing is to put store Real examples are injected into each component
        • Specifically, in the... Of the component beforeCreate In the life cycle mixin Mix in <font color=red>vuexInit</font> Method , So that store Instance injection into each component

          • mixin We need to pay attention to :

              1. Hook functions with the same name will be merged into an array , So all will be called
              1. The hook of the object will be called before the component itself hooks
  • summary :

    • Vue.use(Vuex) => Vuex.install() => applyMixin(Vue) => Vue.mixin({ beforeCreate: vuexInit })
    • adopt Vue.use(Vuex) The ultimate effect is to store Instances are injected into each component , And share the same store example
    • therefore : You can use... In each component this.$store Access to the strore example
    Vuex Medium install Method 
    ----
    
    let Vue // bind on install
    export function install (_Vue) {
    if (Vue && _Vue === Vue) {
      if (__DEV__) { 
        console.error(
          '[vuex] already installed. Vue.use(Vuex) should be called only once.'
        )
      }
      return
      // Vue There is , And with the incoming _Vue equal , And the production environment , return 
    }
    Vue = _Vue
    // Vue non-existent , Just assign the parameters passed in _Vue
    applyMixin(Vue)
    //  call  applyMixin  Method 
    }
applyMixin(Vue)
---
 
export default function (Vue) {
  const version = Number(Vue.version.split('.')[0])

  if (version >= 2) {
    Vue.mixin({ beforeCreate: vuexInit })
    //  Version greater than or equal to 2, Mix in  beforeCreate  Lifecycle hook vuexInit Method 
  } else {
     //  edition 1  It's for compatibility , Don't consider 
  }


  function vuexInit () {
    const options = this.$options
    //  there  this  Represents each component , have  beforeCreate  hook 
  
    // store injection
    if (options.store) {
      // this.$options.store There is 
        // 1.  Description is the root component , That is, through  const vue = new Vue({store: store})  Generated instances vue, That's the root component 
  
      this.$store = typeof options.store === 'function'
        ? options.store()
        : options.store
      //  On the root component, change to  $store  attribute 
        // 1. store Is a function , Call function directly , Return value assignment 
        // 2. store It's an object , Just assign it directly 
    } else if (options.parent && options.parent.$store) {
      this.$store = options.parent.$store
      //  If it's not the root component , And the parent component exists 
        // 1.  because :(  Parent component beforeCreate )  Will be earlier than  (  Subcomponents beforeCreate )  Execute first , In addition to the root component , Of the remaining components  $store  All use parent components  $store
        // 2.  therefore : Resulting in... Of all components  $store  It's all used   Of the root component  $store, The root component uses only its own  $store, That is, layer by layer transmission 
        // 3.  Final : All components use   Of the root component $store ,  That is, they all use the incoming  store  example 
    }
  }
}

(3) Store Class constructor

<font color=blue size=5>this._modules = new ModuleCollection(options)</font>

  • ModuleCollection class

    • The main role

        1. assignment this.root Is the root module
        1. If... Exists in the module modules attribute , To the parent module _children Add the corresponding relationship of the module in the attribute object
    • Instance attributes

      • root

        • namely new Module example
    • Prototype attribute

      • getNamespace: If module Of namespaced Attributes exist , Recursive splicing path
    • Call in constructor this.register([], rawRootModule, false)
    • <font color=blue>this.register([], rawRootModule, false)</font>

      • The main function is :
      • <font color=blue>new Module(rawModule, runtime)</font>

        • Instance attributes

          • Mount on instance <font color=blue>runtime, _children, _rawModule, state</font>
          • _children: The default is an empty object

            • It's used to store the seeds module
          • _rawModule: It's the incoming module

            • Or roots module The incoming Vuex Of options
            • Or modules The individual module
          • state

            • Is a function, call the function to return state object
            • Is a direct assignment to an object
        • Prototype attribute

          • On Prototype <font color=blue>getChild,addChild,namespaced,update,forEachGetter ...</font>
          • getChild: return _children Object module
          • addChild: towards _children Object module
          • namespaced: The module Whether they have namespaced attribute , A Boolean value
          • forEachGetter: loop getters object , take value and key Pass to parameter function
      • <font color=blue>parent.addChild(path[path.length - 1], newModule)</font>

        • To the father module Medium _children Object to add a submodule mapping
      • <font color=blue>array.prototype.reduce The method needs attention </font>

        • [].reduce((a,b) => {...}, params) When an empty array is executed reduce when , Regardless of the first argument, the function performs any logic , Will directly return the second parameter params
        • In the source code, there are a large number of empty arrays to execute reduce The logic of , Such as registration module And register namespce
      • <font color=blue>array.prototype.slice The method needs attention </font>

        • [].slice(0, -1) It returns an empty array

    <font color=red size=5>installModule(this, state, [], this._modules.root)</font>

  • The main role

    1. take module After splicing ( path ) and ( moudle) As key-value Mapping to ( store._modulesNamespaceMap ) In the object
    2. state

      • Will all modules Medium module Medium state, Merge into rootModule Of state in
      • key => moduleName
      • value => moduleState
      • What needs to be noticed is the modification state All need to pass ( store._withCommit ) packing
    3. local

      • structure module.context object , Assign a value to local object , take local The object is passed in as a parameter registerMutation,registerAction,registerGetter
      • local There are dispatch, commit, getters, state Equal attribute
    4. mutations

      • Will all module Medium ( mutations The function in ) All added to store Of ( this._mutations ) In the object

        • key:

          • The module Of namespace + mutations The name of a function in
          • such as cart/incrementItemQuantity namely module name + mutation Function name
        • value

          • An array
          • Members are specific mutation Wrapper function of function ,wrappedMutationHandler (payload)
      • Be careful :

        • <table><tr><td bgcolor=orange>mutations hander</td></tr></table>

          • Parameters

            • The first parameter state

              • The state Is the name of the current local module state, Not the total state
            • The second parameter payload
        • <table><tr><td bgcolor=yellow>commit</td></tr></table>

          • store.commit
          • modify store in state The only way is store.commit(mutations hander)
          • store.commit('increment', 10)
          • store.commit('increment', {amount: 10})
          • store.commit({type: 'increment',amount: 10})
    5. actions

      • Will all module Medium ( actions The function in ) All added to store Of ( this._actions ) In the object
      • key:

        • action.root ? key : namespace + key
        • above key It means actions Medium action Function name
        • above namespace It means moudle name +/
        • such as :cart/addProductToCart namely module name + action Function name
      • value:

        • An array
        • Members are specific action Wrapper function of function ,wrappedActionHandler(payload)
      • Be careful :

        • <table><tr><td bgcolor=orange>action function </td></tr></table>

          • Parameters

            • The first parameter

              • It's a context object , context and store Instances have the same methods and properties
              • context Object has dispatch commit getters state rootGetters rootState These attributes
            • The second parameter

              • payload
        • <table><tr><td bgcolor=yellow>dispatch</td></tr></table>

          • store.dispatch
          • action Function is passed store.dispatch To trigger
          • store.dispatch('increment')
          • store.dispatch('incrementAsync', {amount: 10})
          • store.dispatch({type: 'incrementAsync', amount: 10})
    6. getters

      • Will all module Medium getters All mapped to ( store._wrappedGetters ) On the object

        • key:namespace + key module name /getter function
        • value: A function , namely wrappedGetter (store)
      • <table><tr><td bgcolor=orange>getter function </td></tr></table>

        • Parameters

          • The first parameter : Local state object
          • The second parameter : Local getters object
          • The third parameter : root state
          • Fourth parameter : root getters
          • (state, getters, rootState, rootGetters) => {...}
          • Be careful getter The parameters of the function are in order , and action The function is the first argument. The object has no order
    7. loop moudle Medium ( _children ) object , Execute sequentially ( installModule ) Method
  • <font color=red>store._modules.getNamespace(path)</font>

    • The main role : Splicing module Of path, And then assign it to namespace
  • <font color=red>store._withCommit(fn)</font>

    • Package the incoming mutation function

      • stay mutation When the function executes, the this._committing Set up true
      • stay mutation After the function is executed, the this._committing Set up false
      • This ensures that the changes state Only through mutation function , If you modify it directly, it is not set this._committing by true, It proves not to pass mutation In the revision
  • <font color=red>Vue.set(parentState, moduleName, module.state)</font>

    • take moudles Medium module Medium state Merge to parent state On
  • <font color=red>makeLocalContext(store, namespace, path)</font>

    • Generate local object , have dispatch, commit, getters, state Equal attribute
  • <font color=red>module.forEachMutation(fn)</font>

    • Loop through the current module Medium mutations object , take value and key Pass in fn(mutation, key)
    • Splicing namespace and key
    • call registerMutation(store, namespacedType, mutation, local)
  • <font color=red>registerMutation(store, namespacedType, mutation, local)</font>

    • towards ( store._mutations[type] ) Array push One ( mutation Wrapper functions )

      1. store._mutations[type]

        • type yes namespace/getter The function name of the function , Like this cart/incrementItemQuantity
        • value yes wrappedMutationHandler (payload) A function like this
      2. The wrapper function is to mutation Function to add a shell , Pass in payload, Call inside again mutation handle function
      3. Be careful :

        • store._mutations It's an object
        • Every store._mutations[type] Is an array
  • <font color=red>module.forEachAction(fn)</font>

    • and module.forEachMutation(fn) similar
    • Loop through the current module Medium actions object

      • use ( action.root ) To get a different type
      • use ( action.handle ) Whether there is a value to be assigned handle
      • call registerAction(store, type, handler, local) function
  • <font color=red>registerAction(store, type, handler, local)</font>

    • towards ( store._actions[type] ) Array push One ( action Wrapper functions wrappedActionHandler )
  • <font color=red>wrappedActionHandler(payload)</font>

    • Will be called internally ( action perhaps action.handler ) The function is named handler function
    • If handler The return value of the function is not promise, The return value is converted to promise object , and resolve, Then return fulfilled State of promise object
    • <font color=red>handler()</font>

      • binding this To store
      • Parameters :

        • The first parameter is an object , Has the following properties

          • dispatch
          • commit
          • getters
          • state
          • rootGetters
          • rootState
        • The second parameter is payload
  • <font color=red>module.forEachGetter(fn)</font>

    • Loop traversal module Medium getters All in getter
    • take value and key Pass to fn(value, key)
  • <font color=red>registerGetter(store, namespacedType, getter, local)</font>

    • to ( store._wrappedGetters ) Add attribute method

      • key => namespace + key
      • value => wrappedGetter
    • <font color=red>wrappedGetter</font>

      • getter The wrapper function of ,(store) => getter(localState, localGetters, rootState, rootGetters)
      • Parameters : Local state getters, root state getter

<font color=DarkOrchid>commit (_type, _payload, _options) </font>

  • I have mainly done the following things :

    1. If the first parameter is an object , Modification parameters

      • ( One ) store.commit('increment', {amount: 10})
      • ( Two ) store.commit({type: 'increment',amount: 10})
      • Transform the second into the first
    2. Look for the mutaation, That is to say this._mutations Pass through key seek mutation Corresponding [mutation]

      • No error found
      • Find the... Stored in the circular array mutation Wrapper functions , It will call the real mutation handler function
    3. Shallow copy this._subscribers Then iterate over the array , Call the subscribe function => Change state After that, you need to respond to the view, etc
  • subscribe

    • subscribe(handler: Function): Function

      • store.subscribe((mutation, state) => {...})
      • subscribe store Of mutation
      • handler Will be in every mutation After completion, call , receive mutation And what happened mutation After the state as a parameter
      • To stop the subscription , Call the function returned by this method to stop the subscription

<font color=DarkOrchid>dispatch (_type, _payload) </font>

  • I have mainly done the following things

    1. Modification parameters
    2. perform this._actionSubscribers Medium before Crocheted action Monitor function

      • before Indicates that the call time of the subscription processing function should be in a action Call before distribution
    3. perform action function

      • this._actions[type] The array length is greater than 1, Just promise.all packing , Guarantee result It's all promise all resolve
      • this._actions[type] Array length is less than or equal to 1, Call directly
    4. When all promise all resolve after , That is, after all asynchronies return results , perform _actionSubscribers in after Crocheted action Monitor function

      • after Indicates that the call time of the subscription processing function should be in a action After distribution, call
    5. And the final result resolve get out , return promise object
  • subscribeAction

    • subscribeAction(handler: Function): Function

      • store.subscribeAction((action, state) => {...})
      • subscribe store Of action
      • handler Will be in every action Called and received at the time of distribution action Description and current store Of state These two parameters
      • Be careful :

        • from 3.1.0 rise ,subscribeAction You can also specify that the call time of the subscription processing function should be in a action Before or after distribution ( The default behavior is before )
        • You can specify store.subscribeAction({before: (action, state) => {},after: (action, state) => {}})

Source code

(1) this._modules = new ModuleCollection(options)

  • The main role

    • assignment this.root Is the root module
    • If... Exists in the module modules attribute , To the parent module _children Add the corresponding relationship of the module in the attribute object
//  File directory  src/module/module-collection.js

export default class ModuleCollection {
  constructor (rawRootModule) {
    // register root module (Vuex.Store options)
    this.register([], rawRootModule, false)
  }
}

---

register (path, rawModule, runtime = true) {
    if (__DEV__) {
      assertRawModule(path, rawModule)
      // dev Environmental Science , Assert incoming options Each of the attribute objects in key For corresponding value The type of , If it's not what it should be , Just throw it wrong 
    }

    // dev Environment and incoming options Meet assertion requirements   perhaps  prod Environmental Science 
    //  Then enter the following code 

    const newModule = new Module(rawModule, runtime) // ----------------------------------------  analysis 1
    //  Create a  module  example 
    // module
      //  Instance attributes  runtime, _children, _rawModule, state
      //  Prototype attribute   getChild,addChild,namespaced,update,forEachGetter ... etc. 

    if (path.length === 0) {
      this.root = newModule
      //  The length of the array is 0, Just  new Module Instance assigned to  root  attribute 
    } else {
      //  problem : When path.length !== 0
      //  answer : The following will determine whether there is  modules  attribute , When it exists modules Attribute , Will call... Again register, At this time, the length is not 0

      const parent = this.get(path.slice(0, -1)) // --------------------------------------------  analysis 2
      // parent: Get parent module

      parent.addChild(path[path.length - 1], newModule) // -------------------------------------  analysis 3
      //  To the father module._children Add to object key and value, The father modlue The son of module
    }

    if (rawModule.modules) {
      //  If it's time to module in modules object , Will traverse modules
      forEachValue(rawModule.modules, (rawChildModule, key) => { // ---------------------------  analysis 4
        this.register(path.concat(key), rawChildModule, runtime)
        // path.concat(key) =>  similar  [module1],[module2], Be careful concat Will not change path
        // rawChildModule   => module1 module2
      })
    }
  }
  • analysis 1

    //  File directory  src/module/module.js
    
    export default class Module {
    constructor (rawModule, runtime) {
      this.runtime = runtime
      // Store some children item
      this._children = Object.create(null)
      // _children  object 
        //  For storage  modules  All children in the property moudle
        // key => moudle Name 
        // value => module object , There could be state,mutations,actions,getters etc. 
    
      // Store the origin module object which passed by programmer
      this._rawModule = rawModule
      //  The current module 
    
      const rawState = rawModule.state
    
      // Store the origin module's state
      this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
      // state
        //  function : Just call , return stated object 
        //  object : Direct assignment 
    }
    
     addChild (key, module) {
      this._children[key] = module
    }
    
    getChild (key) {
      return this._children[key]
    }
    }
  • analysis 2

    get (path) {
      //  When path When is an empty array ,[].reducer((a,b) => a.getChild(key), this.root) return  return root
      return path.reduce((module, key) => {
        return module.getChild(key)
      }, this.root)
    }
  • analysis 3

    addChild (key, module) {
      this._children[key] = module
      //  to module Example of _children Add a mapping to the property object 
      // key => moudle name 
      // value => module object 
    }
  • analysis 4

    export function forEachValue (obj, fn) {
    Object.keys(obj).forEach(key => fn(obj[key], key))
    //  Traverse obj
      // fn(value, key)
      //  take obj Medium  key  As the second parameter 
      //  take obj Medium  value  As the first parameter 
    }

(2) Store Class constructor

Store Class constructor  - src/stroe.js
---

export class Store {
  constructor (options = {}) {
    // Auto install if it is not done yet and `window` has `Vue`.
    // To allow users to avoid auto-installation in some cases,
    // this code should be placed here. See #731
    if (!Vue && typeof window !== 'undefined' && window.Vue) {
      install(window.Vue)
      //  If  Vue non-existent   also  window There is   also  window.Vue There is , Install automatically 
    }

    if (__DEV__) {
      assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
      assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
      assert(this instanceof Store, `store must be called with the new operator.`)
      //  Assertion 
        // Vue.use(Vuex) Must be in  new Vuex.store()  Pre invocation 
        //  Whether there is promise, because vuex rely on promise
        // Store Must pass new Command invocation 
    }

    const {
      plugins = [], //  Deconstruct assignment  plugins  and  strict, And assign default value 
      strict = false
    } = options

    // store internal state
    this._committing = false  
    // _committing
      //  Sign a , The default is false
      //  modify state Can use  _withCommit  Function packing , In the implementation of the modification state Function ,this._committing=true, Set as after modification false
      //  Used to determine whether to pass mutation Function modification state, Only through mutation modify state It's legal 
      //  For example, in the merger state It will be used in the operation of 
  
    this._actions = Object.create(null)
    // _actions
      //  Store all moudle Of action
      // {
      //   cart/addProductToCart: [ƒ], f refer to  wrappedActionHandler (payload) 
      //   cart/checkout: [ƒ],
      //   products/getAllProducts: [],
      // }
  
    this._actionSubscribers = []
    // _actionSubscribers
      //  collect action Monitor function 
      //  Pay attention to distinguish between :
        // this._subscribers = [] mutation Monitor function 
  

    this._mutations = Object.create(null)
    // _mutations
      //  Store all moudle Of mutation
      // {
      //   cart/incrementItemQuantity: [ƒ], f refer to wrappedMutationHandler
      //   cart/pushProductToCart: [ƒ],
      //   cart/setCartItems: [ƒ],
      //   cart/setCheckoutStatus: [ƒ],
      //   products/decrementProductInventory: [ƒ],
      //   products/setProducts: [ƒ],
      // }
  
    this._wrappedGetters = Object.create(null)
    // _wrappedGetters
      //  Store all module Medium getter
      // {
      //   cart/cartProducts: ƒ wrappedGetter(store)
      //   cart/cartTotalPrice: ƒ wrappedGetter(store)
      // }
      //  Be careful :!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        // 1. this._wrappedGetters  stay resetStoreVM(this, state) use  
        // 2.  Pay attention to distinguish between  ( store.getters )  and  ( store._wrappedGetters)

    this._modules = new ModuleCollection(options)
    // _modules
      // ModuleCollection  Class is mainly to collect all moudle
      // {
      //   root: {
      //     runtime: false,
      //     state: {}, //  Note that there is no merge at this time state
      //     _children: { //  hold moudules Medium module Placed in parent module  _children  Properties of the 
      //       cart: Module,
      //       products: Module,
      //     },
      //     _rawModule:  root module
      //   }
      // }

    this._modulesNamespaceMap = Object.create(null)
    // _modulesNamespaceMap
      // namespace  and  mdule  One to one mapping object 
      // {
      //   cart/: Module 
      //   products/: Module
      // }

    this._subscribers = []
    // _subscribers
      // mutation Monitor function 
      //  Pay attention to distinguish between :
        // this._actionSubscribers = [] action Monitor function 
  
    this._watcherVM = new Vue()
    this._makeLocalGettersCache = Object.create(null)

    // bind commit and dispatch to self
    const store = this
    const { dispatch, commit } = this
    this.dispatch = function boundDispatch (type, payload) {
      return dispatch.call(store, type, payload)
      //  binding dispatch Functional this To store For instance 
    }
    this.commit = function boundCommit (type, payload, options) {
      return commit.call(store, type, payload, options)
       //  binding commit Functional this To store For instance 
    }

    // strict mode
    this.strict = strict
    //  Strict mode 
      //  The default is  flase
      //  In strict mode , Only through mutation modify state
      //  In a production environment, it is recommended to shut down , Because strict mode will deeply monitor the status tree to detect non-conforming status changes , There is a loss of performance 

    const state = this._modules.root.state
    //  root state

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

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    resetStoreVM(this, state) // -----------------------------------------------------------  The following analysis 

    // apply plugins
    plugins.forEach(plugin => plugin(this))
    //  Loop traversal plug-in , Pass in stroe

    const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
    //  Whether there is : Pass in  new Vuex.stroe(options) Medium options in devtools
    //  There is :options.devtools
    //  non-existent :Vue.config.devtools

    if (useDevtools) {
      devtoolPlugin(this)
    }
  }
}

(3) installModule(this, state, [], this._modules.root)

function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length
  //  When path The length of the array is 0, It's roots module

  const namespace = store._modules.getNamespace(path)
  //  obtain  namespace => module name  + /
  //                =>  perhaps  ''

  // register in namespace map
  if (module.namespaced) {
    // module.namespaced
      //  Every module There can be namespaced attribute , It's a Boolean value 
      //  Indicates that the local is turned on module name 
      //  Namespace official website introduction  (https://vuex.vuejs.org/zh/guide/modules.html)

    if (store._modulesNamespaceMap[namespace] && __DEV__) {
      console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
      //  repeated 
    }
    store._modulesNamespaceMap[namespace] = module
    // _modulesNamespaceMap
      //  establish  module  and  nameSpace  The mapping relation of 
      // key : namespace
      // vlaue: module
        // {
        //   cart/: Module 
        //   products/: Module
        // }
  }

  // set state
  if (!isRoot && !hot) {
    //  Not a root module   also  hot by flase, Will enter judgment 
  
    const parentState = getNestedState(rootState, path.slice(0, -1))
    // parentState
    //  Gets the name of the module 
    const moduleName = path[path.length - 1]
    store._withCommit(() => {
      if (__DEV__) {
        if (moduleName in parentState) {
          console.warn(
            `[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`
          )
        }
      }
      Vue.set(parentState, moduleName, module.state)
      //  Merge all modules Medium state To rootState
    })
  }

  const local = module.context = makeLocalContext(store, namespace, path)
  //  Statement  module.context  And the assignment 
  // local
    // dispatch
    // commit
    // getters
    // state

  module.forEachMutation((mutation, key) => {
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
    //  Put all the modules Each of them mutations In the mutation Function added to  _mutations  On the object 
    // _mutations  The object has the following format 
    // {
    //   cart/incrementItemQuantity: [ƒ], f refer to wrappedMutationHandler
    //   cart/pushProductToCart: [ƒ],
    //   cart/setCartItems: [ƒ],
    //   cart/setCheckoutStatus: [ƒ],
    //   products/setProducts: [f],
    // }
  })

  module.forEachAction((action, key) => {
    const type = action.root ? key : namespace + key
    const handler = action.handler || action
    registerAction(store, type, handler, local)
    //  Put all the module Each of them actions In the action Function added to  _actions  On the object 
    // _actions  The object has the following format 
    // {
    //   cart/addProductToCart: [ƒ], f refer to  wrappedActionHandler (payload) 
    //   cart/checkout: [ƒ]
    //   products/getAllProducts: []
    // }
  })

  module.forEachGetter((getter, key) => {
    const namespacedType = namespace + key
    registerGetter(store, namespacedType, getter, local)
    //  Put all the modul Each of them getter Function added to  _wrappedGetters  On the object 
    //  Store all module Medium getter
    // {
    //   cart/cartProducts: ƒ wrappedGetter(store)
    //   cart/cartTotalPrice: ƒ wrappedGetter(store)
    // }
  })

  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
    //  Loop traversal module._children object , And execute... In each cycle  installModule  Method 
  })
}

(4) resetStoreVM(this, this.state, hot)

function resetStoreVM (store, state, hot) {
  // resetStoreVM
    //  Parameters 
      // store
      // state
      // hot

  const oldVm = store._vm
  // oldVm  Cache old store._vm

  // bind store public getters
  store.getters = {}
  //  stay  store  Add... To the instance  getters  Property object , The initial value is an empty object 
  //  Be careful :
    // 1.  distinguish  store._wrappedGetters  and  store.getters

  // reset local getters cache
  store._makeLocalGettersCache = Object.create(null)

  const wrappedGetters = store._wrappedGetters
  // wrappedGetters
    //  cache  store._wrappedGetters

  const computed = {}
  // Statement computed Variable 

  forEachValue(wrappedGetters, (fn, key) => {
    //  loop wrappedGetters, take  value  and  key  Pass in as a parameter forEachValue The second parameter function of 

    computed[key] = partial(fn, store)
    // 1. partial It's such a function 
      // function partial (fn, arg) {
      //   return function () {
      //     return fn(arg)
      //   }
      // }
    // 2.  namely  computed[key] = () => fn(store) 
      // fn  It's concrete getter function 

    Object.defineProperty(store.getters, key, {
      get: () => store._vm[key],
      enumerable: true //  enumerable 
    })
    // 1.  to  store.getters  Object to add  key  attribute 
    // 2.  visit  stroe.getters[key] = store._vm[key]
      //  By visiting  store.getter.aaaa  It's equivalent to visiting  store._vm.aaaa
  })

  // use a Vue instance to store the state tree
  // suppress warnings just in case the user has added
  // some funky global mixins
  const silent = Vue.config.silent
  //  cache  Vue.config.silent

  Vue.config.silent = true
  //   Turn on cancel warning 
  //  Cancel  Vue  All the logs and warnings , That is to say new Vue() There will be no warning when 

  store._vm = new Vue({
    data: {
      ?state: state // 11.  Will the incoming state Assign a value to data Medium  ?state  attribute 
    },
    computed // 22.  take computed Change wolf assigned to Vue Medium computed attribute  (computed[key] = () => fn(store))
  })
  // store._vm = new Vue(...)
    //  Go over it  1122  bring  state and () => fn(store)  Responsive 
  
  Vue.config.silent = silent
  //  Turn off cancel warning 

  // enable strict mode for new vm
  if (store.strict) {
    enableStrictMode(store)
    //  Enable strict mode , Guarantee to modify store Only through mutation
  }

  if (oldVm) {
    if (hot) {
      // dispatch changes in all subscribed watchers
      // to force getter re-evaluation for hot reloading.
      store._withCommit(() => {
        oldVm._data.?state = null
        //  Remove references 
      })
    }
    Vue.nextTick(() => oldVm.$destroy())
    // dom After the update , destroy vue example 
      // const oldVm = store._vm
      // store._vm = new Vue()
  }
}

(5) commit

commit (_type, _payload, _options) {
    // check object-style commit
    const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options)
    //  structure commit Required parameter type 
    // 1. store.commit('increment', 10)
    // 2. store.commit('increment', {amount: 10}) 
    // 3. store.commit({type: 'increment',amount: 10})
    //  That's to put the first  3  The second case is constructed as  2  Two kinds of situations 

    const mutation = { type, payload }

    const entry = this._mutations[type]
    // entry  Find what needs to be submitted mutation Function , Is an array , An array is a wrapped mutation handle function 

    if (!entry) {
      //  I didn't find the mutation
      if (__DEV__) {
        console.error(`[vuex] unknown mutation type: ${type}`)
      }
      return
    }

    this._withCommit(() => {
      // this._withCommit  Guarantee to modify state It's a legal means , namely  this._committing Modifying yes yes true
      entry.forEach(function commitIterator (handler) {
        handler(payload)
        //  Pass in parameters to execute mutation handle  function 
      })
    })

    this._subscribers
      .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
      .forEach(sub => sub(mutation, this.state))
      //  Shallow copy  this._subscribers  Then iterate over the array , Call the subscribe function  
      //  Change state After that, you need to respond to the view, etc 

    if (
      __DEV__ &&
      options && options.silent
    ) {
      console.warn(
        `[vuex] mutation type: ${type}. Silent option has been removed. ` +
        'Use the filter functionality in the vue-devtools'
      )
    }
  }

(6) dispatch

dispatch (_type, _payload) {
    // check object-style dispatch
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)
    //  structure commit Required parameter type 
    // 1. store.dispatch('increment')
    // 2. store.dispatch('incrementAsync', {amount: 10})
    // 3. store.dispatch({type: 'incrementAsync', amount: 10})
    //  That's to put the first  3  The second case is constructed as  2  Two kinds of situations 

    const action = { type, payload }
    const entry = this._actions[type]
    if (!entry) {
      //  I didn't find the action
      if (__DEV__) {
        console.error(`[vuex] unknown action type: ${type}`)
      }
      return
    }

    try {
      this._actionSubscribers
        .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
        .filter(sub => sub.before) 
        .forEach(sub => sub.before(action, this.state))
        // before hook action Monitor function 
        // before  Indicates that the call time of the subscription processing function should be in a  action  Call before distribution 
    } catch (e) {
      if (__DEV__) {
        console.warn(`[vuex] error in before action subscribers: `)
        console.error(e)
      }
    }

    const result = entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)
    //  Longer than 1,promise.all() Guarantee result all resolve
    //  Length less than or equal to 1, Call directly 

    return new Promise((resolve, reject) => {
      result.then(res => {
        try {
          this._actionSubscribers
            .filter(sub => sub.after)
            .forEach(sub => sub.after(action, this.state))
          // after  Indicates that the call time of the subscription processing function should be in a  action  After distribution, call 
        } catch (e) {
          if (__DEV__) {
            console.warn(`[vuex] error in after action subscribers: `)
            console.error(e)
          }
        }
        resolve(res)
        // resolve final result 
      }, error => {
        try {
          this._actionSubscribers
            .filter(sub => sub.error)
            .forEach(sub => sub.error(action, this.state, error))
        } catch (e) {
          if (__DEV__) {
            console.warn(`[vuex] error in error action subscribers: `)
            console.error(e)
          }
        }
        reject(error)
      })
    })
  }

(7) mapstate

  • First of all mapstate How to use

     Examples of the official website 
    
    computed: {
    ...mapState('some/nested/module', {
      a: state => state.a,
      b: state => state.b
    })
    }
    
     When the name of the mapped calculated property is the same as  state  The names of the child nodes of are the same , We can also give  mapState  Pass an array of strings 
    computed: mapState([
    //  mapping  this.count  by  store.state.count
    'count'
    ])
  • Source code

    • hold state Constructed as computed Object returns
    • according to namespace hold ( Local state and getter As a parameter ) Passed to the passed in parameter object ( Property function )
    export const mapState = normalizeNamespace((namespace, states) => {
    // normalizeNamespace
      //  Return the modified parameters ,f(namespace, states)
      //  Change to the following parameter form 
        // ...mapState('some/nested/module', {
        //   a: state => state.a,
        //   b: state => state.b
        // })
    const res = {}
    if (__DEV__ && !isValidMap(states)) {
      //  If it is dev Environmental Science   also  states  Not an object or an array 
      //  Report errors 
    
      // function isValidMap (map) {
      //   return Array.isArray(map) || isObject(map)
      // }
      console.error('[vuex] mapState: mapper parameter must be either an Array or an Object')
    }
    
    normalizeMap(states).forEach(({ key, val }) => {
    // 1.  If states It's an array , Returns an array , Each member is an object ({ key: key, val: key })
    // 2.  If states It's the object , Returns an array , Each member is an object ({ key:key, val: map[key] })
      res[key] = function mappedState () {
        let state = this.$store.state
        let getters = this.$store.getters
        if (namespace) {
          const module = getModuleByNamespace(this.$store, 'mapState', namespace)
          // module
            //  stay  ( store._modulesNamespaceMap )  Find... In the object  (  Parameters namespace )  Corresponding  ( module )
    
          if (!module) {
            return
          }
    
          state = module.context.state //  Get the module Local of species state
          getters = module.context.getters //  Get the module Local of species getters
        }
        return typeof val === 'function'
          ? val.call(this, state, getters)
          : state[val]
        // val It's a function , Just call the function val.call(this, state, getters) return 
        // val It's not a function , Go straight back  state[val]
      }
      // mark vuex getter for devtools
      res[key].vuex = true
    })
    return res
    //  Finally back to res object 
    // res Object will be used as a component  computed
    })
    
------

function normalizeNamespace (fn) {
  return (namespace, map) => {
    if (typeof namespace !== 'string') {
      //  If  namespace  Not a string 
        //  Description: only one parameter was passed in 
        //  Just put namespace='', Assign the first parameter that is not a string to the second parameter 
      map = namespace
      namespace = ''
    } else if (namespace.charAt(namespace.length - 1) !== '/') {
      namespace += '/'
      //  No,  /  Then add 
    }
    return fn(namespace, map)
    //  Returns the value after the conversion parameter  fn
  }
}
------

function getModuleByNamespace (store, helper, namespace) {
  // 1. getModuleByNamespace(this.$store, 'mapState', namespace)

  const module = store._modulesNamespaceMap[namespace]
  //  find namespace Corresponding module
  if (__DEV__ && !module) {
    console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`)
  }
  return module
  //  return module
}
------

function normalizeMap (map) {
  if (!isValidMap(map)) {
    return []
    //  Not an array or object , Returns an array by default 
  }
  return Array.isArray(map)
    ? map.map(key => ({ key, val: key }))
    : Object.keys(map).map(key => ({ key, val: map[key] }))
  // 1.  If it's an array , Returns an array , Each member is an object ({ key: key, val: key })
  // 2.  If it's an object , Returns an array , Each member is an object ({ key:key, val: map[key] })
}

Precautions in use

mapState - ( belt namespace And without namespace)

mapGetters

mapMutations

mapActions

  • ui In the component

    <template>
    <div class="vuex">
      <div>
        <div style="font-size: 30px">vuex</div>
        <div>dispatch One action => store.dispatch({type: 'actionName', payload: ''})</div>
        <div>commit One mutation => store.dispatch({type: 'actionName', payload: ''})</div>
       
        <div>------</div>
        <button @click="changeCount"> Click Modify vuexModule Medium count+1 </button>
        <div>{{moduleState.count}}</div>
    
        <div>------</div>
        <div><button @click="getName"> Click on , Initiate request , obtain name data , utilize Vuex actions -  Don't pass parameters </button></div>
        <div><button @click="getName2"> Click on , Initiate request , obtain name data , utilize Vuex actions -  The ginseng </button></div>
        <div>{{moduleState.name}}</div>
      </div>
    </div>
    </template>
    
    <script> import { mapState, mapGetters, mapMutations, mapActions } from "vuex"; export default { name: "vuex", data() { return {}; }, computed: { ...mapState({ rootState: state => { // ---------------  Name it  rootState return state; // -----------------------  there state yes rootMoudule Of state } }), ...mapState("vuexModule", { // ------------ namespace moduleState: state => { // --------------  Name it  moduleState return state; // ----------------------  there state yes moudles in vuexModule Of state } }), ...mapGetters("vuexModule", { // ----------  The second parameter is the object , That is, you can modify getter Name  changeGetterName: "square" }), ...mapGetters("vuexModule", ['square']), //  The second parameter is the array  }, methods: { ...mapMutations('vuexModule', { addCountChangeName: 'AddCount' // -------  Object mode , You can modify mutation Name  }), ...mapActions('vuexModule', ['getData', 'getData2']), // mapActions changeCount() { this.addCountChangeName(1) // -----------  Parameters will be used as mutation(state, payload) Of payload }, getName() { this.getData() // -----------------------  Do not pass to action function , Deal with asynchronous  }, getName2() { this.getData2({ // -----------------------  Pass the reference to action function , Deal with asynchronous  url: "/home", method: "get" }) } }, mounted() { console.log( this.rootState.vuexModule.count, " No, namespace Of mapState Acquired state -  visit coount" ); console.log( this.moduleState.count, " have namespace Of mapState Acquired state -  visit count" ); console.log(this.changeGetterName, 'mapGetters The second parameter is the object '); console.log(this.square, 'mapGetters The second parameter is the array '); } }; </script>
    
  • store/vuexModule

    import {getName} from '../../api/home'
    
    const vuex = {
    namespaced: true,
    state() {
      return {
        count: 2,
        name: 'init name'
      }
    },
    getters: {
      square(state, getters, rootState, rootGetters) {
        console.log(state, 'state')
        console.log(getters, 'getters')
        console.log(rootState, 'rootState')
        console.log(rootGetters, 'rootGetters')
        return (state.count * rootState.rootCount)
      }
    },
    mutations: {
      AddCount(state, payload) {
        state.count = state.count + payload
      },
      getNameData(state, payload) {
        state.name = payload
      }
    },
    actions: {
      async getData(context) { // dispatch Don't wear ginseng action function 
        const {commit} = context
        const res = await getName({
          url: "/home",
          method: "get"
        });
        if (res && res.data && res.data.name) {
          console.log(res.data.name, '2222')
          commit('getNameData', res.data.name)
        }
      },
      async getData2(context, payload) { 
        // dispatch Wear ginseng to action function 
        // action(context, payload)
          //  The first parameter : context and store Have the same api
          //  The second parameter :payload yes dispatch One action Pass through parameters 
        const {commit} = context
        const res = await getName(payload);
        // const res = await getName({
        //   url: "/home",
        //   method: "get"
        // });
        if (res && res.data && res.data.name) {
          console.log(res.data.name, '2222')
          commit('getNameData', res.data.name)
        }
      },
    }
    };
    
    export default vuex

Information

vuex Official document https://vuex.vuejs.org/zh/

Chuan Shen More comprehensive https://juejin.im/post/684490...
2.3.1 edition Vuex, The process is detailed :https://juejin.im/post/684490...
yck https://juejin.im/post/684490...

copyright notice
author[woow_ wu7],Please bring the original link to reprint, thank you.
https://en.qdmana.com/2021/08/20210825093335322b.html

Random recommended