国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

你想要的——vuex源碼分析

gggggggbong / 3557人閱讀

摘要:大家好,今天給大家?guī)?lái)的是源碼分析,希望能夠能跟大家進(jìn)行交流,歡迎提意見(jiàn),寫的不好的地方歡迎拍磚源碼地址首先我們先來(lái)看看是如何跟項(xiàng)目一起結(jié)合使用的,以下是官方中的一個(gè)簡(jiǎn)單例子我們必須先創(chuàng)建一個(gè)將這個(gè)傳給的實(shí)例,這樣我們就能夠在中獲取到這個(gè)并

大家好,今天給大家?guī)?lái)的是vuex(2.3.1)源碼分析,希望能夠能跟大家進(jìn)行交流,歡迎提意見(jiàn),寫的不好的地方歡迎拍磚

[github源碼地址][1]

首先我們先來(lái)看看vuex是如何跟vue項(xiàng)目一起結(jié)合使用的,以下是官方demo中的一個(gè)簡(jiǎn)單例子

(1)我們必須先創(chuàng)建一個(gè)store

import Vue from "vue"
import Vuex from "vuex"
import { state, mutations } from "./mutations"
import plugins from "./plugins"

Vue.use(Vuex)

export default new Vuex.Store({
  state,
  mutations,
  plugins
})

(2)將這個(gè)store傳給vue的實(shí)例,這樣我們就能夠在vue中獲取到這個(gè)store并且使用它的功能了

import "babel-polyfill"
import Vue from "vue"
import store from "./store"
import App from "./components/App.vue"

new Vue({
  store, // inject store to all children
  el: "#app",
  render: h => h(App)
})

以上就是vuex的簡(jiǎn)單使用方法,然后接下來(lái)我們就開(kāi)始來(lái)分析vuex的源碼吧

目錄結(jié)構(gòu)

從目錄結(jié)構(gòu)可以看出,vuex是一個(gè)代碼比較簡(jiǎn)潔的框架

index.js——入口文件

import { Store, install } from "./store"
import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from "./helpers"

export default {
  Store,
  install,
  version: "__VERSION__",
  mapState,
  mapMutations,
  mapGetters,
  mapActions,
  createNamespacedHelpers
}

入口文件只做了一件事,就是導(dǎo)入了其他相關(guān)的文件,并且將vuex的功能export出去,相當(dāng)于定義vuex對(duì)外使用的API

store.js——vuex的倉(cāng)庫(kù),也是vuex中比較重要的一環(huán)
這個(gè)文件比較長(zhǎng),我們可以一點(diǎn)一點(diǎn)來(lái)分析:
總體來(lái)說(shuō),這個(gè)文件做了幾件事,定義并導(dǎo)出了Store這個(gè)類和install方法,并執(zhí)行了install這個(gè)方法,我們都知道,vue的所有插件都是通過(guò)install這個(gè)方法來(lái)安裝的

import applyMixin from "./mixin"
import devtoolPlugin from "./plugins/devtool"
import ModuleCollection from "./module/module-collection"
import { forEachValue, isObject, isPromise, assert } from "./util"

一開(kāi)始導(dǎo)入相關(guān)的方法,后面會(huì)解釋這些方法的用處

let Vue // 定義了變量Vue,為的是引用外部的vue構(gòu)造函數(shù),這樣vuex框架就可以不用導(dǎo)入vue這個(gè)庫(kù)了

----------------------------------------------------------這是分割線----------------------------------------------------------------------------------------

接下來(lái)是定義Store這個(gè)類,從圖中可以看出這個(gè)vuex中的外store對(duì)外提供的能力,包括常用的commit,dispatch,watch等

先看看構(gòu)造函數(shù)吧:

constructor (options = {}) {
    // 這個(gè)是在開(kāi)發(fā)過(guò)程中的一些環(huán)節(jié)判斷,vuex要求在創(chuàng)建vuex store實(shí)例之前必須先使用這個(gè)方法Vue.use(Vuex)來(lái)安裝vuex,項(xiàng)目必須也得支持promise,store也必須通過(guò)new來(lái)創(chuàng)建實(shí)例
    
    if (process.env.NODE_ENV !== "production") {
      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.`)
    }
    
    // 從參數(shù)options中結(jié)構(gòu)出相關(guān)變量
    const {
      plugins = [],
      strict = false
    } = options

    let {
      state = {}
    } = options
    
    // 這個(gè)簡(jiǎn)單的,不解釋
    if (typeof state === "function") {
      state = state()
    }

    // store internal state
    // 初始化store內(nèi)部狀態(tài),Object.create(null)可以創(chuàng)建一個(gè)干凈的空對(duì)象
    this._committing = false
    this._actions = Object.create(null)
    this._mutations = Object.create(null)
    this._wrappedGetters = Object.create(null)
    // vuex支持模塊,即將state通過(guò)key-value的形式拆分為多個(gè)模塊
    // 模塊的具體內(nèi)容可以查看這里 :https://vuex.vuejs.org/en/mutations.html
    this._modules = new ModuleCollection(options)
    this._modulesNamespaceMap = Object.create(null)
    // 監(jiān)聽(tīng)隊(duì)列,當(dāng)執(zhí)行commit時(shí)會(huì)執(zhí)行隊(duì)列中的函數(shù)
    this._subscribers = []
    // 創(chuàng)建一個(gè)vue實(shí)例,利用vue的watch的能力,可以監(jiān)控state的改變,具體后續(xù)watch方法會(huì)介紹
    this._watcherVM = new Vue()

    // bind commit and dispatch to self
    
    const store = this
    // 緩存dispatch和commit方法
    const { dispatch, commit } = this
    // 定義dispatch方法
    this.dispatch = function boundDispatch (type, payload) {
      return dispatch.call(store, type, payload)
    }
    // 定義commit方法
    this.commit = function boundCommit (type, payload, options) {
      return commit.call(store, type, payload, options)
    }

    // strict mode
    // 定義嚴(yán)格模式,不要在發(fā)布環(huán)境下啟用嚴(yán)格模式!嚴(yán)格模式會(huì)深度監(jiān)測(cè)狀態(tài)樹(shù)來(lái)檢測(cè)不合規(guī)的狀態(tài)變更——請(qǐng)確保在發(fā)布環(huán)境下關(guān)閉嚴(yán)格模式,以避免性能損失。
    // 具體后續(xù)enableStrictMode方法會(huì)提到
    this.strict = strict

    // init root module.
    // this also recursively registers all sub-modules
    // and collects all module getters inside this._wrappedGetters
    // 這個(gè)作者的注釋已經(jīng)寫得挺明白,就是初始化根模塊,遞歸注冊(cè)子模塊,收集getter
    installModule(this, state, [], this._modules.root)

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    // 初始化store中的state,使得state變成響應(yīng)式的,原理就是將state作為一個(gè)vue實(shí)例的data屬性傳入,具體在分析這個(gè)函數(shù)的時(shí)候會(huì)介紹
    resetStoreVM(this, state)

    // apply plugins
    // 執(zhí)行插件,這個(gè)是一個(gè)數(shù)組,所以遍歷他,然后執(zhí)行每個(gè)插件的函數(shù)
    plugins.concat(devtoolPlugin).forEach(plugin => plugin(this))
  }

呼呼呼~ 至此,終于把store類全部讀完了,休息五分鐘,然后繼續(xù)往下看哈。

接下來(lái)關(guān)于state的獲取和設(shè)置

  // 獲取state,  直接返回內(nèi)部data的$$state
  get state () {
    return this._vm._data.$$state
  }

  set state (v) {
    if (process.env.NODE_ENV !== "production") {
      assert(false, `Use store.replaceState() to explicit replace store state.`)
    }
  }

commit是vuex中一個(gè)比較重要的操作,因?yàn)樗梢杂|發(fā)mutation修改對(duì)state的修改,并且是同步執(zhí)行的

commit (_type, _payload, _options) {
    // check object-style commit
    // 首先統(tǒng)一傳入?yún)?shù)的格式,主要是針對(duì)當(dāng)type是個(gè)對(duì)象的情況,需要把這個(gè)對(duì)象解析出來(lái)
    const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options)
    
    // 緩存本次commit操作的類型和負(fù)荷,以供后續(xù)監(jiān)聽(tīng)隊(duì)列(this._subscribers)使用
    const mutation = { type, payload }
    // 獲取相關(guān)的type的mutation函數(shù),我們都知道,在vuex中都是通過(guò)commit一個(gè)類型然后觸發(fā)相關(guān)的mutation函數(shù)來(lái)操作state的,所以在此必須獲取相關(guān)的函數(shù)
    const entry = this._mutations[type]
    if (!entry) {
      if (process.env.NODE_ENV !== "production") {
        console.error(`[vuex] unknown mutation type: ${type}`)
      }
      return
    }
    // 在_withCommit中觸發(fā)上面獲取的mutation函數(shù),簡(jiǎn)單粗暴使用數(shù)組的forEach執(zhí)行哈哈,之所以要在外面包一層_withCommit,是表明操作的同步性
    this._withCommit(() => {
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })
    // 這個(gè)就是之前說(shuō)的監(jiān)聽(tīng)隊(duì)列,在每次執(zhí)行commit函數(shù)時(shí)都會(huì)遍歷執(zhí)行一下這個(gè)隊(duì)列
    this._subscribers.forEach(sub => sub(mutation, this.state))

    if (
      process.env.NODE_ENV !== "production" &&
      options && options.silent
    ) {
      console.warn(
        `[vuex] mutation type: ${type}. Silent option has been removed. ` +
        "Use the filter functionality in the vue-devtools"
      )
    }
  }

dispatch是跟commit有點(diǎn)相似的函數(shù),但是commit必須是同步的,而dispatch是異步的,內(nèi)部還是必須通過(guò)commit來(lái)操作state

dispatch (_type, _payload) {
    // check object-style dispatch
    // 同上面commit,不解釋
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)
    
    // 因?yàn)閐ispatch觸發(fā)的是actions中的函數(shù),所以這里獲取actions相關(guān)函數(shù),過(guò)程類似commit
    const entry = this._actions[type]
    if (!entry) {
      if (process.env.NODE_ENV !== "production") {
        console.error(`[vuex] unknown action type: ${type}`)
      }
      return
    }
    // 因?yàn)閐ispatch支持異步,所以這里作者使用Promise.all來(lái)執(zhí)行異步函數(shù)并且判斷所有異步函數(shù)是否都已經(jīng)執(zhí)行完成,所以在文件最開(kāi)始判斷了當(dāng)前環(huán)境必須支持promise就是這個(gè)原因
    return entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)
  }

subscribe函數(shù),這是pub/sub模式在vuex中的一個(gè)運(yùn)用,用戶可以通過(guò)subscribe函數(shù)來(lái)監(jiān)聽(tīng)state的變化,函數(shù)返回一個(gè)取消監(jiān)聽(tīng)的函數(shù),便于用戶在合適的時(shí)機(jī)取消訂閱

subscribe (fn) {
    const subs = this._subscribers
    if (subs.indexOf(fn) < 0) {
      subs.push(fn)
    }
    // 返回取消訂閱的函數(shù),通過(guò)函數(shù)額splice方法,來(lái)清除函數(shù)中不需要的項(xiàng)
    return () => {
      const i = subs.indexOf(fn)
      if (i > -1) {
        subs.splice(i, 1)
      }
    }
  }

watch函數(shù),響應(yīng)式地監(jiān)測(cè)一個(gè) getter 方法的返回值,當(dāng)值改變時(shí)調(diào)用回調(diào)函數(shù),原理其實(shí)就是利用vue中的watch方法

watch (getter, cb, options) {
    if (process.env.NODE_ENV !== "production") {
      assert(typeof getter === "function", `store.watch only accepts a function.`)
    }
    // 在上面構(gòu)造函數(shù)中,我們看到this._watcherVM就是一個(gè)vue的實(shí)例,所以可以利用它的watch來(lái)實(shí)現(xiàn)vuex的watch,原理都一樣,當(dāng)監(jiān)聽(tīng)的值或者函數(shù)的返回值發(fā)送改變的時(shí)候,就觸發(fā)相應(yīng)的回調(diào)函數(shù),也就是我們傳入的cb參數(shù),options則可以來(lái)讓監(jiān)聽(tīng)立即執(zhí)行&深度監(jiān)聽(tīng)對(duì)象
    return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)
  }

replaceState,根據(jù)名字就可知道,是替換當(dāng)前的state

replaceState (state) {
    this._withCommit(() => {
      this._vm._data.$$state = state
    })
  }

registerModule函數(shù),可以使用 store.registerModule 方法注冊(cè)模塊

registerModule (path, rawModule) {
    if (typeof path === "string") path = [path]

    if (process.env.NODE_ENV !== "production") {
      assert(Array.isArray(path), `module path must be a string or an Array.`)
      assert(path.length > 0, "cannot register the root module by using registerModule.")
    }
    //其實(shí)內(nèi)部時(shí)候通過(guò),register方法,遞歸尋找路徑,然后將新的模塊注冊(cè)root模塊上,具體后續(xù)介紹到module的時(shí)候會(huì)詳細(xì)分析
    this._modules.register(path, rawModule)
    //安裝模塊,因?yàn)槊總€(gè)模塊都有他自身的getters,actions, modules等,所以,每次注冊(cè)模塊都必須把這些都注冊(cè)上,后續(xù)介紹installModule的時(shí)候,會(huì)詳細(xì)介紹到
    installModule(this, this.state, path, this._modules.get(path))
    // reset store to update getters...
    // 重置VM
    resetStoreVM(this, this.state)
  }

unregisterModule函數(shù),上述registerModule函數(shù)的相反操作,具體在module的時(shí)候會(huì)介紹到,在此了解個(gè)大概,先不糾結(jié)細(xì)節(jié)

unregisterModule (path) {
    if (typeof path === "string") path = [path]

    if (process.env.NODE_ENV !== "production") {
      assert(Array.isArray(path), `module path must be a string or an Array.`)
    }

    this._modules.unregister(path)
    this._withCommit(() => {
      const parentState = getNestedState(this.state, path.slice(0, -1))
      // 利用vue.delete方法,確保模塊在被刪除的時(shí)候,視圖能監(jiān)聽(tīng)到變化
      Vue.delete(parentState, path[path.length - 1])
    })
    resetStore(this)
  }

hotUpdate函數(shù),Vuex 支持在開(kāi)發(fā)過(guò)程中熱重載 mutation、modules、actions、和getters

hotUpdate (newOptions) {
    this._modules.update(newOptions)
    resetStore(this, true)
  }

_withCommit函數(shù),從函數(shù)名可以看出這是一個(gè)內(nèi)部方法,作用就是保證commit過(guò)程中執(zhí)行的方法都是同步的

_withCommit (fn) {
    // 保存原來(lái)的committing的狀態(tài)
    const committing = this._committing
    //將想在的committing狀態(tài)設(shè)置為true
    this._committing = true
    //執(zhí)行函數(shù)
    fn()
    //將committing狀態(tài)設(shè)置為原來(lái)的狀態(tài)
    this._committing = committing
  }

到目前為止,我們已經(jīng)看完了Store這個(gè)類的所有代碼~慢慢消化,然后繼續(xù)往下

----------------------------------------------------------這又是分割線----------------------------------------------------------------------------------------

接下來(lái),我們分析一下,一些其他的輔助方法,跟上面store的一些內(nèi)容會(huì)有相關(guān)。ready? Go

resetStore函數(shù),用于重置整個(gè)vuex中的store,從代碼中可以看出,這個(gè)函數(shù)主要的功能,就是將傳入的store實(shí)例的_actions,_mutations,_wrappedGetters,_modulesNamespaceMap置為空,然后重新安裝模塊和重置VM,此方法在上述熱更新和注銷模塊的時(shí)候會(huì)使用到

function resetStore (store, hot) {
  store._actions = Object.create(null)
  store._mutations = Object.create(null)
  store._wrappedGetters = Object.create(null)
  store._modulesNamespaceMap = Object.create(null)
  const state = store.state
  // init all modules
  installModule(store, state, [], store._modules.root, true)
  // reset vm
  resetStoreVM(store, state, hot)
}

resetStoreVM函數(shù),這個(gè)用于重置store中的vm,所謂vm,指的就是視圖模型,也就是常見(jiàn)mvvm中的vm,在此指的是將state作為data中$$state屬性的一個(gè)vue實(shí)例

function resetStoreVM (store, state, hot) {
   // 保存原有store的_vm
  const oldVm = store._vm
    
  // bind store public getters
  store.getters = {}
  // store的_wrappedGetters緩存了當(dāng)前store中所有的getter
  const wrappedGetters = store._wrappedGetters
  const computed = {}
  //遍歷這個(gè)對(duì)象,獲取每個(gè)getter的key和對(duì)應(yīng)的方法
  forEachValue(wrappedGetters, (fn, key) => {
    // use computed to leverage its lazy-caching mechanism
    // 將getter以key-value的形式緩存在變量computed中,其實(shí)后面就是將getter作為vue實(shí)例中的計(jì)算屬性
    computed[key] = () => fn(store)
    // 當(dāng)用戶獲取getter時(shí),相當(dāng)于獲取vue實(shí)例中的計(jì)算屬性,使用es5的這個(gè)Object.defineProperty方法做一層代理
    Object.defineProperty(store.getters, key, {
      get: () => store._vm[key],
      enumerable: true // for local getters
    })
  })

  // 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
  // silent設(shè)置為true,則取消了所有的警告和日志,眼不見(jiàn)為凈
  Vue.config.silent = true
  
  // 將傳入的state,作為vue實(shí)例中的data的$$state屬性,將剛剛使用computed變量搜集的getter,作為實(shí)例的計(jì)算屬性,所以當(dāng)state和getter都變成了響應(yīng)式的了
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
  Vue.config.silent = silent

  // enable strict mode for new vm
    
  if (store.strict) {
    //如果設(shè)置了嚴(yán)格模式則,不允許用戶在使用mutation以外的方式去修改state
    enableStrictMode(store)
  }

  if (oldVm) {
    if (hot) {
      // dispatch changes in all subscribed watchers
      // to force getter re-evaluation for hot reloading.
      store._withCommit(() => {
        // 將原有的vm中的state設(shè)置為空,所以原有的getter都會(huì)重新計(jì)算一遍,利用的就是vue中的響應(yīng)式,getter作為computed屬性,只有他的依賴改變了,才會(huì)重新計(jì)算,而現(xiàn)在把state設(shè)置為null,所以計(jì)算屬性重新計(jì)算
        oldVm._data.$$state = null
      })
    }
    // 在下一次周期銷毀實(shí)例
    Vue.nextTick(() => oldVm.$destroy())
  }
}

installModule函數(shù),用于安裝模塊,注冊(cè)相應(yīng)的mutation,action,getter和子模塊等

function installModule (store, rootState, path, module, hot) {
   //判斷是否為根模塊
  const isRoot = !path.length
   //根據(jù)路徑生成相應(yīng)的命名空間
  const namespace = store._modules.getNamespace(path)

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

  // set state
  if (!isRoot && !hot) {
    // 將模塊的state設(shè)置為響應(yīng)式
    const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    store._withCommit(() => {
      Vue.set(parentState, moduleName, module.state)
    })
  }
  //設(shè)置本地上下文,主要是針對(duì)模塊的命名空間,對(duì)dispatch,commit,getters和state進(jìn)行修改,后面講到makeLocalContext的時(shí)候會(huì)詳細(xì)分析,現(xiàn)在只需要知道,這個(gè)操作讓用戶能夠直接獲取到對(duì)象子模塊下的對(duì)象就可以了
  const local = module.context = makeLocalContext(store, namespace, path)
 
  //將mutation注冊(cè)到模塊上
  module.forEachMutation((mutation, key) => {
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
  })
  //將action注冊(cè)到模塊上  
  module.forEachAction((action, key) => {
    const namespacedType = namespace + key
    registerAction(store, namespacedType, action, local)
  })
  //將getter注冊(cè)到模塊上
  module.forEachGetter((getter, key) => {
    const namespacedType = namespace + key
    registerGetter(store, namespacedType, getter, local)
  })
  //遞歸安裝子模塊  
  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })
}

makeLocalContext函數(shù),就是installModule中設(shè)置本地上下文的具體實(shí)現(xiàn)

function makeLocalContext (store, namespace, path) {
   //如果沒(méi)有命名空間,則是使用全局store上的屬性,否則對(duì)store上的屬性進(jìn)行本地化處理
  const noNamespace = namespace === ""

  const local = {
    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
      //dispatch的本地化處理,就是修改type
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args

      if (!options || !options.root) {
         //在type前面加上命名空間
        type = namespace + type
        if (process.env.NODE_ENV !== "production" && !store._actions[type]) {
          console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
          return
        }
      }
        //調(diào)用store上的dispatch方法
      return store.dispatch(type, payload)
    },

    commit: noNamespace ? store.commit : (_type, _payload, _options) => {
    // commit的本地化修改跟dispatch相似,也是只是修改了type,然后調(diào)用store上面的commit
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args

      if (!options || !options.root) {
        type = namespace + type
        if (process.env.NODE_ENV !== "production" && !store._mutations[type]) {
          console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
          return
        }
      }

      store.commit(type, payload, options)
    }
  }

  // getters and state object must be gotten lazily
  // because they will be changed by vm update
   //gettters和state的修改,則依賴于makeLocalGetters函數(shù)和getNestedState函數(shù),后面會(huì)分析
  Object.defineProperties(local, {
    getters: {
      get: noNamespace
        ? () => store.getters
        : () => makeLocalGetters(store, namespace)
    },
    state: {
      get: () => getNestedState(store.state, path)
    }
  })

  return local
}

makeLocalGetters函數(shù),則是對(duì)getter進(jìn)行本地化處理

function makeLocalGetters (store, namespace) {
  const gettersProxy = {}

  const splitPos = namespace.length
  Object.keys(store.getters).forEach(type => {
    //這里獲取的每個(gè)type都是一個(gè)有命名空間+本地type的字符串,例如: type的值可能為 “m1/m2/”+"typeName"
    // skip if the target getter is not match this namespace
    if (type.slice(0, splitPos) !== namespace) return

    // extract local getter type
    const localType = type.slice(splitPos)

    // Add a port to the getters proxy.
    // Define as getter property because
    // we do not want to evaluate the getters in this time.
    //相當(dāng)于做了一層代理,將子模塊的localType映射到store上的type
    Object.defineProperty(gettersProxy, localType, {
      get: () => store.getters[type],
      enumerable: true
    })
  })

  return gettersProxy
}

registerMutation函數(shù),就是注冊(cè)mutation的過(guò)程,將相應(yīng)type的mutation推到store._mutations[type]的隊(duì)列中,當(dāng)commit這個(gè)type的時(shí)候就觸發(fā)執(zhí)行隊(duì)列中的函數(shù)

function registerMutation (store, type, handler, local) {
  const entry = store._mutations[type] || (store._mutations[type] = [])
  entry.push(function wrappedMutationHandler (payload) {
    handler(local.state, payload)
  })
}

registerAction函數(shù),注冊(cè)action的過(guò)程,原理類似于registerMutation,不同點(diǎn)在于action支持異步,所以必須用promise進(jìn)行包裝

function registerAction (store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload, cb) {
    let res = handler({
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload, cb)
    if (!isPromise(res)) {
      res = Promise.resolve(res)
    }
    if (store._devtoolHook) {
      return res.catch(err => {
        store._devtoolHook.emit("vuex:error", err)
        throw err
      })
    } else {
      return res
    }
  })
}

registerGetters函數(shù),根據(jù)type,將getter方法掛載在store._wrappedGetters[type]下面

function registerGetter (store, type, rawGetter, local) {
  if (store._wrappedGetters[type]) {
    if (process.env.NODE_ENV !== "production") {
      console.error(`[vuex] duplicate getter key: ${type}`)
    }
    return
  }
  store._wrappedGetters[type] = function wrappedGetter (store) {
    // 為子模塊的getter提供了這個(gè)四個(gè)參數(shù),方便用戶獲取,如果是根模塊,則local跟store取出來(lái)的state和getters相同
    return rawGetter(
      local.state, // local state
      local.getters, // local getters
      store.state, // root state
      store.getters // root getters
    )
  }
}

enableStrictMode函數(shù)則是在嚴(yán)格模式下,不允許state被除mutation之外的其他操作修改,代碼比較簡(jiǎn)單,利用vue的$watch方法實(shí)現(xiàn)的

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

getNestedState函數(shù),獲取對(duì)應(yīng)路徑下的state

function getNestedState (state, path) {
  return path.length
    ? path.reduce((state, key) => state[key], state)
    : state
}

unifyObjectStyle函數(shù),作用是調(diào)整參數(shù),主要是當(dāng)type是一個(gè)對(duì)象的時(shí)候,對(duì)參數(shù)進(jìn)行調(diào)整

function unifyObjectStyle (type, payload, options) {
  if (isObject(type) && type.type) {
    options = payload
    payload = type
    type = type.type
  }

  if (process.env.NODE_ENV !== "production") {
    assert(typeof type === "string", `Expects string as the type, but found ${typeof type}.`)
  }

  return { type, payload, options }
}

以上是相關(guān)輔助函數(shù)的全部?jī)?nèi)容,你看明白了么~

----------------------------------------------------------這依然是分割線------------------------------------------------------------------------------------

文件的最后,就是定義了install函數(shù),然后自動(dòng)執(zhí)行了這個(gè)函數(shù),讓vuex能夠在項(xiàng)目中運(yùn)作起來(lái)

export function install (_Vue) {
  if (Vue) {
    if (process.env.NODE_ENV !== "production") {
      console.error(
        "[vuex] already installed. Vue.use(Vuex) should be called only once."
      )
    }
    return
  }
  Vue = _Vue
  //在vue的生命周期中初始化vuex,具體實(shí)現(xiàn)后面講到mixin.js這個(gè)文件時(shí)會(huì)說(shuō)明
  applyMixin(Vue)
}

// auto install in dist mode
if (typeof window !== "undefined" && window.Vue) {
  install(window.Vue)
}

以上就是store.js的所有內(nèi)容啦~

----------------------------------------------------------嚴(yán)肅分割線------------------------------------------------------------------------------------

接下來(lái)講解有關(guān)module目錄下的內(nèi)容,該目錄有兩個(gè)文件分別是module-collection.js和module.js,這兩個(gè)文件主要是有關(guān)于vuex中模塊的內(nèi)容;
首先我們看看module-collection.js,這個(gè)文件主要導(dǎo)出一個(gè)ModuleCollection類:
構(gòu)造函數(shù)

constructor (rawRootModule) {
    // register root module (Vuex.Store options)
    //主要是注冊(cè)根模塊,我們?cè)谥皊tore的構(gòu)造函數(shù)中曾經(jīng)使用到 this._modules = new ModuleCollection(options),注冊(cè)一個(gè)根模塊然后緩存在this._module中
    this.register([], rawRootModule, false)
  }

緊接著看看下面register函數(shù),它用于注冊(cè)模塊

register (path, rawModule, runtime = true) {
    if (process.env.NODE_ENV !== "production") {
      assertRawModule(path, rawModule)
    }
    // 創(chuàng)建一個(gè)新模塊,具體會(huì)在后面講到Module的時(shí)候分析
    const newModule = new Module(rawModule, runtime)
    // 判讀是否為根模塊
    if (path.length === 0) {
      this.root = newModule
    } else {
      //根據(jù)path路徑,利用get方法獲取父模塊  
      const parent = this.get(path.slice(0, -1))
      //為父模塊添加子模塊
      parent.addChild(path[path.length - 1], newModule)
    }

    // register nested modules
    // 如果當(dāng)前模塊里面有子模塊,則遞歸的去注冊(cè)子模塊
    if (rawModule.modules) {
      forEachValue(rawModule.modules, (rawChildModule, key) => {
        this.register(path.concat(key), rawChildModule, runtime)
      })
    }
  }

相反,unregister函數(shù)則是移除一個(gè)模塊

unregister (path) {
    // 通過(guò)get方法獲取父模塊
    const parent = this.get(path.slice(0, -1))
    //獲取需要?jiǎng)h除的模塊的名稱,即他的key
    const key = path[path.length - 1]
    if (!parent.getChild(key).runtime) return
    //利用module中removeChild方法刪除該模塊,其實(shí)就是delete了對(duì)象上的一個(gè)key
    parent.removeChild(key)
  }

get函數(shù),其實(shí)就是利用es5中數(shù)組reduce方法,從根模塊開(kāi)始根據(jù)傳入的path來(lái)獲取相應(yīng)的子模塊

get (path) {
    return path.reduce((module, key) => {
      return module.getChild(key)
    }, this.root)
  }

getNamespace函數(shù),利用傳入的參數(shù)path,生成相應(yīng)的命名空間,實(shí)現(xiàn)的原理跟上述的get方法類似

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

upate方法,就是更新模塊,具體看下面update方法的實(shí)現(xiàn)

update (rawRootModule) {
    update([], this.root, rawRootModule)
  }

以上就是整個(gè)ModuleCollection類的實(shí)現(xiàn)

接下來(lái)講解一下function update的實(shí)現(xiàn)

function update (path, targetModule, newModule) {
  if (process.env.NODE_ENV !== "production") {
    assertRawModule(path, newModule)
  }

  // update target module
  //目標(biāo)模塊更新為新模塊,具體實(shí)現(xiàn)是將原有模塊的namespaced,actions,mutations,getters替換為新模塊的namespaced,actions,mutations,getters
  // 具體會(huì)在Module類中update方法講解
  targetModule.update(newModule)

  // update nested modules
  // 如果新的模塊有子模塊,則遞歸更新子模塊
  if (newModule.modules) {
    for (const key in newModule.modules) {
      if (!targetModule.getChild(key)) {
        if (process.env.NODE_ENV !== "production") {
          console.warn(
            `[vuex] trying to add a new module "${key}" on hot reloading, ` +
            "manual reload is needed"
          )
        }
        return
      }
      update(
        path.concat(key),
        targetModule.getChild(key),
        newModule.modules[key]
      )
    }
  }
}

至于assertRawModule方法和makeAssertionMessage方法,就是一些簡(jiǎn)單的校驗(yàn)和提示,不影響主流程&代碼比較簡(jiǎn)單,這里不做贅述

以上就是整個(gè)module-collection.js文件的所有內(nèi)容

接下來(lái)就應(yīng)該分析目錄中的另一個(gè)文件module.js,這個(gè)文件主要導(dǎo)出一個(gè)Module類,這個(gè)類主要描述了vuex中模塊的功能

構(gòu)造函數(shù),主要做了一些模塊初始化的事情

//構(gòu)造函數(shù),主要做了一些模塊初始化的事情
  constructor (rawModule, runtime) {
    //緩存運(yùn)行時(shí)的標(biāo)志
    this.runtime = runtime
    //創(chuàng)建一個(gè)空對(duì)象來(lái)保存子模塊
    this._children = Object.create(null)
    //緩存?zhèn)魅氲哪K
    this._rawModule = rawModule
    //緩存?zhèn)魅肽K的state,如果state是一個(gè)函數(shù),則執(zhí)行這個(gè)函數(shù)
    const rawState = rawModule.state
    this.state = (typeof rawState === "function" ? rawState() : rawState) || {}
  }

namespaced函數(shù)是主要就是獲取當(dāng)前模塊是否是命名模塊,vuex支持命名模塊和匿名模塊

get namespaced () {
    return !!this._rawModule.namespaced
  }

addChild,removeChild,getChild這三個(gè)函數(shù)就分別是添加,刪除,獲取子模塊,內(nèi)容比較簡(jiǎn)單,不贅述

update方法,將原有緩存模塊的namespaced,actions,mutations,getters替換成新傳入模塊的namespaced,actions,mutations,getters

update (rawModule) {
    this._rawModule.namespaced = rawModule.namespaced
    if (rawModule.actions) {
      this._rawModule.actions = rawModule.actions
    }
    if (rawModule.mutations) {
      this._rawModule.mutations = rawModule.mutations
    }
    if (rawModule.getters) {
      this._rawModule.getters = rawModule.getters
    }
  }

forEachChild函數(shù),利用util中forEachValue方法,變量每個(gè)子模塊,將每個(gè)子模塊作為傳入的回調(diào)函數(shù)參數(shù),然后執(zhí)行回調(diào)函數(shù)

forEachChild (fn) {
    forEachValue(this._children, fn)
  }

forEachGetter,forEachAction,forEachMutation代碼邏輯跟上述forEachChild十分類似,不在贅述

以上就是module.js文件的所有內(nèi)容,至此我們也已經(jīng)全部分析完module目錄下的所有代碼了

---------------------------------------------------一本正經(jīng)分割線--------------------------------------------------------------------------------
接下來(lái),我們?cè)倏纯磆elp.js這個(gè)文件,這個(gè)文件主要是提供了一些幫助性的方法,使得用戶在使用vuex的過(guò)程中體驗(yàn)更好,更加方便

首先我們先看看文件后面三個(gè)函數(shù):normalizeMap,normalizeNamespace,getModuleByNamespace

normalizeMap函數(shù),這個(gè)方法的作用是格式化傳入的對(duì)象

function normalizeMap (map) {
  // 如果傳入的對(duì)象是數(shù)組,則放回一個(gè)每一項(xiàng)都是key-val對(duì)象的數(shù)組,其中key和val的值相同
  // 如果出入的是一個(gè)對(duì)象,則變量這個(gè)對(duì)象,放回一個(gè)每一項(xiàng)都是key-val數(shù)組,其中key對(duì)應(yīng)對(duì)象的key,val對(duì)應(yīng)相應(yīng)key的值
  return Array.isArray(map)
    ? map.map(key => ({ key, val: key }))
    : Object.keys(map).map(key => ({ key, val: map[key] }))
}

normalizeNamespace函數(shù),調(diào)整參數(shù),格式化命名空間

function normalizeNamespace (fn) {
  return (namespace, map) => {
    //如果沒(méi)傳入命名空間,或者傳入的命名空間不是一個(gè)字符串,則丟棄該參數(shù)
    if (typeof namespace !== "string") {
      map = namespace
      namespace = ""
    } else if (namespace.charAt(namespace.length - 1) !== "/") {
      //否則判斷命名空間后面是否有加上‘/’,如果沒(méi)有則加上
      namespace += "/"
    }
    //最后執(zhí)行傳入的回調(diào)函數(shù)
    return fn(namespace, map)
  }
}

getModuleByNamespace函數(shù),通過(guò)命名空間來(lái)獲取模塊

function getModuleByNamespace (store, helper, namespace) {
  // 返回store._modulesNamespaceMap緩存的模塊
  const module = store._modulesNamespaceMap[namespace]
  if (process.env.NODE_ENV !== "production" && !module) {
    console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`)
  }
  return module
}

mapState函數(shù),我們可以通過(guò)這個(gè)方法將state解構(gòu)到vue項(xiàng)目中去,使其變成vue實(shí)例中的計(jì)算屬性

export const mapState = normalizeNamespace((namespace, states) => {
  //定義一個(gè)空對(duì)象
  const res = {}
  normalizeMap(states).forEach(({ key, val }) => {
    //收集states的所有key,對(duì)應(yīng)key的值,改變成一個(gè)mappedState方法,符合計(jì)算屬性的特點(diǎn)
    res[key] = function mappedState () {
      //獲取store的state和getters
      let state = this.$store.state
      let getters = this.$store.getters
      //如果存在命名空間,則將命名空間下子模塊的state和getters覆蓋原來(lái)store的state和getters
      if (namespace) {
        const module = getModuleByNamespace(this.$store, "mapState", namespace)
        if (!module) {
          return
        }
        state = module.context.state
        getters = module.context.getters
      }
      //如果對(duì)應(yīng)的val是函數(shù)則執(zhí)行,否則返回state下的值
      return typeof val === "function"
        ? val.call(this, state, getters)
        : state[val]
    }
    // mark vuex getter for devtools
    res[key].vuex = true
  })
  //返回這個(gè)包裝過(guò)state的對(duì)象,這個(gè)對(duì)象可以結(jié)構(gòu)成vue中的計(jì)算屬性
  return res
})

mapMutations函數(shù),則是將mutation解構(gòu)到vue實(shí)例中的methods中,使得用戶可以直接調(diào)用methods中的方法來(lái)執(zhí)行store.commit

export const mapMutations = normalizeNamespace((namespace, mutations) => {
  //定義一個(gè)空對(duì)象
  const res = {}
  normalizeMap(mutations).forEach(({ key, val }) => {
    val = namespace + val
    res[key] = function mappedMutation (...args) {
      if (namespace && !getModuleByNamespace(this.$store, "mapMutations", namespace)) {
        return
      }
      //調(diào)用了store中的commit方法,觸發(fā)相應(yīng)的mutation函數(shù)的執(zhí)行
      return this.$store.commit.apply(this.$store, [val].concat(args))
    }
  })
  return res
})

mapGetters的邏輯跟mapState類似,mapActions的邏輯跟mapMutations類似,這里不再贅述

自此,我們把help.js的內(nèi)容也分析完了

---------------------------------------------------一本正經(jīng)分割線--------------------------------------------------------------------------------
接下來(lái)我們看看mixin.js文件
還記得之前store.js里面有個(gè)install方法么,這個(gè)方法就用到了mixin.js文件提供的內(nèi)容

// 這個(gè)文件其實(shí)就導(dǎo)出了一個(gè)方法,供vuex在被引入的時(shí)候,能夠順利安裝到項(xiàng)目中
export default function (Vue) {
  // 首先,判斷vue版本,不同的vue版本,生命周期不同,所以需要做差異處理
  const version = Number(Vue.version.split(".")[0])

  if (version >= 2) {
    // 如果版本是2.0以上的,則在vue的beforeCreate生命周期中,觸發(fā)vuex的初始化
    // 利用的是vue中全局混入的形式
    Vue.mixin({ beforeCreate: vuexInit })
  } else {
    // override init and inject vuex init procedure
    // for 1.x backwards compatibility.
    // 如果是1.x版本的話,就改寫原有Vue原型上的_init方法
    // 先將原來(lái)的函數(shù)保存在常量_init中
    const _init = Vue.prototype._init
    Vue.prototype._init = function (options = {}) {
      options.init = options.init
        ? [vuexInit].concat(options.init)
        : vuexInit
        // 將初始化方法作為原有init的參數(shù)傳入,所以在vue初始化的時(shí)候就會(huì)執(zhí)行vuexInit方法來(lái)初始化vuex
      _init.call(this, options)
    }
  }

  /**
   * Vuex init hook, injected into each instances init hooks list.
   */
  // vuex的初始化鉤子 
  function vuexInit () {
    const options = this.$options
    // store injection
    // 注入store
    if (options.store) {
      this.$store = typeof options.store === "function"
        ? options.store()
        : options.store
    } else if (options.parent && options.parent.$store) {
      this.$store = options.parent.$store
    }
  }
}

plugins文件夾中,主要是關(guān)于插件相關(guān)的內(nèi)容

devtool.js,是關(guān)于是當(dāng)用戶開(kāi)啟vue-devtools時(shí),觸發(fā)了一些操作

// 通過(guò)全局變量__VUE_DEVTOOLS_GLOBAL_HOOK__,判斷是否開(kāi)啟vue-devtools
const devtoolHook =
  typeof window !== "undefined" &&
  window.__VUE_DEVTOOLS_GLOBAL_HOOK__

export default function devtoolPlugin (store) {
  if (!devtoolHook) return

  store._devtoolHook = devtoolHook
  // vue-devtool自身實(shí)現(xiàn)了一套事件機(jī)制,有興趣可以看看其中的實(shí)現(xiàn)
  devtoolHook.emit("vuex:init", store)

  devtoolHook.on("vuex:travel-to-state", targetState => {
    //用targetState替換當(dāng)前的state
    store.replaceState(targetState)
  })
  // 當(dāng)觸發(fā)commit的時(shí)候執(zhí)行這個(gè)方法
  store.subscribe((mutation, state) => {
    // devtoolHook會(huì)emit一個(gè)vuex:mutation事件
    devtoolHook.emit("vuex:mutation", mutation, state)
  })
}

logger.js是在開(kāi)發(fā)過(guò)程中記錄日志的插件

// Credits: borrowed code from fcomb/redux-logger
// 引入深拷貝方法
import { deepCopy } from "../util"

export default function createLogger ({
  collapsed = true,
  filter = (mutation, stateBefore, stateAfter) => true,
  transformer = state => state,
  mutationTransformer = mut => mut
} = {}) {
  return store => {
    // 保存原有的state
    let prevState = deepCopy(store.state)
    // 監(jiān)聽(tīng)state的變化
    store.subscribe((mutation, state) => {
      if (typeof console === "undefined") {
        return
      }
      //深拷貝并且獲取新的state
      const nextState = deepCopy(state)

      if (filter(mutation, prevState, nextState)) {
        // 獲取當(dāng)前時(shí)間
        const time = new Date()
        // 格式化時(shí)間
        const formattedTime = ` @ ${pad(time.getHours(), 2)}:${pad(time.getMinutes(), 2)}:${pad(time.getSeconds(), 2)}.${pad(time.getMilliseconds(), 3)}`
        // 格式化mutation
        const formattedMutation = mutationTransformer(mutation)
        // 獲取輸出的信息
        const message = `mutation ${mutation.type}${formattedTime}`
        // 在 Web控制臺(tái)上創(chuàng)建一個(gè)新的分組.隨后輸出到控制臺(tái)上的內(nèi)容都會(huì)被添加一個(gè)縮進(jìn)
        const startMessage = collapsed
          ? console.groupCollapsed
          : console.group

        // render
        try {
          // 輸出日志
          startMessage.call(console, message)
        } catch (e) {
          console.log(message)
        }

        console.log("%c prev state", "color: #9E9E9E; font-weight: bold", transformer(prevState))
        console.log("%c mutation", "color: #03A9F4; font-weight: bold", formattedMutation)
        console.log("%c next state", "color: #4CAF50; font-weight: bold", transformer(nextState))

        try {
          console.groupEnd()
        } catch (e) {
          console.log("—— log end ——")
        }
      }
      // 替換state
      prevState = nextState
    })
  }
}

至于util.js,內(nèi)部提供一些簡(jiǎn)單的工具方法,不再贅述啦~可自行研究
---------------------------------------------------最后的分割線--------------------------------------------------------------------------------

以上,便是vuex源碼的所有內(nèi)容。。不管寫的怎么樣,你都看到這里啦,對(duì)此深表感謝~

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/84022.html

相關(guān)文章

  • Vue源碼解析(五)-vuex

    摘要:提供了函數(shù),它把直接映射到我們的組件中,先給出的使用值為值為讓我們看看的源碼實(shí)現(xiàn)規(guī)范當(dāng)前的命名空間。在中,都是同步事務(wù)。同步的意義在于這樣每一個(gè)執(zhí)行完成后都可以對(duì)應(yīng)到一個(gè)新的狀態(tài)和一樣,這樣就可以打個(gè)存下來(lái),然后就可以隨便了。 Vue 組件中獲得 Vuex 狀態(tài) 按官網(wǎng)說(shuō)法:由于 Vuex 的狀態(tài)存儲(chǔ)是響應(yīng)式的,從 store 實(shí)例中讀取狀態(tài)最簡(jiǎn)單的方法就是在計(jì)算屬性中返回某個(gè)狀態(tài),本...

    calx 評(píng)論0 收藏0
  • 太原面經(jīng)分享:如何在vue面試環(huán)節(jié),展示晉級(jí)阿里P6+技術(shù)功底?

    摘要:假如你通過(guò)閱讀源碼,掌握了對(duì)的實(shí)現(xiàn)原理,對(duì)生態(tài)系統(tǒng)有了充分的認(rèn)識(shí),那你會(huì)在面試環(huán)節(jié)游刃有余,達(dá)到晉級(jí)阿里的技術(shù)功底,從而提高個(gè)人競(jìng)爭(zhēng)力,面試加分更容易拿。 前言 一年一度緊張刺激的高考開(kāi)始了,與此同時(shí),我也沒(méi)閑著,奔走在各大公司的前端面試環(huán)節(jié),不斷積累著經(jīng)驗(yàn),一路升級(jí)打怪。 最近兩年,太原作為一個(gè)準(zhǔn)二線城市,各大互聯(lián)網(wǎng)公司的技術(shù)棧也在升級(jí)換代,假如你在太原面試前端崗位,而你的技術(shù)庫(kù)里若...

    xiaoqibTn 評(píng)論0 收藏0
  • Vuex源碼學(xué)習(xí)(一)功能梳理

    摘要:我們通常稱之為狀態(tài)管理模式,用于解決組件間通信的以及多組件共享狀態(tài)等問(wèn)題。創(chuàng)建指定命名空間的輔助函數(shù),總結(jié)的功能首先分為兩大類自己的實(shí)例使用為組件中使用便利而提供的輔助函數(shù)自己內(nèi)部對(duì)數(shù)據(jù)狀態(tài)有兩種功能修改數(shù)據(jù)狀態(tài)異步同步。 what is Vuex ? 這句話我想每個(gè)搜索過(guò)Vuex官網(wǎng)文檔的人都看到過(guò), 在學(xué)習(xí)源碼前,當(dāng)然要有一些前提條件了。 了解Vuex的作用,以及他的使用場(chǎng)景。 ...

    livem 評(píng)論0 收藏0
  • [源碼學(xué)習(xí)] Vuex

    摘要:為了更清楚的理解它的原理和實(shí)現(xiàn),還是從源碼開(kāi)始讀起吧。結(jié)構(gòu)梳理先拋開(kāi),的主要源碼一共有三個(gè)文件,,初始化相關(guān)用到了和我們使用創(chuàng)建的實(shí)例并傳遞給的根組件。這個(gè)方法的第一個(gè)參數(shù)是構(gòu)造器。的中,在保證單次調(diào)用的情況下,調(diào)用對(duì)構(gòu)造器進(jìn)入了注入。 原文鏈接 Vuex 作為 Vue 官方的狀態(tài)管理架構(gòu),借鑒了 Flux 的設(shè)計(jì)思想,在大型應(yīng)用中可以理清應(yīng)用狀態(tài)管理的邏輯。為了更清楚的理解它的原理和...

    FreeZinG 評(píng)論0 收藏0
  • vuex源碼分析(一)

    摘要:的源碼分析系列大概會(huì)分為三篇博客來(lái)講,為什么分三篇呢,因?yàn)閷懸黄嗔恕_@段代碼檢測(cè)是否存在,如果不存在那么就調(diào)用方法這行代碼用于確保在我們實(shí)例化之前,已經(jīng)存在。每一個(gè)集合也是的實(shí)例。 vuex的源碼分析系列大概會(huì)分為三篇博客來(lái)講,為什么分三篇呢,因?yàn)閷懸黄嗔恕D粗M(fèi)勁,我寫著也累showImg(https://segmentfault.com/img/bVXKqD?w=357&...

    keithyau 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<