日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

VUEX源码学习笔记(第5~6章 共6章)

發布時間:2024/4/17 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 VUEX源码学习笔记(第5~6章 共6章) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

第五章 輔助函數

在第一章我們曾經說過:

VUEX采用的是典型的IIFE(立即執行函數表達式)模式,當代碼被加載(通過<script>或Vue.use())后,VUEX會返回一個對象,這個對象包含了Store類、install方法、mapState輔助函數、mapMutations輔助函數、mapGetters輔助函數、mapActions輔助函數、createNamespacedHelpers輔助函數以及當前的版本號version。

本章就將詳細講解mapState、mapMutations、mapGetters、mapActions、createNamespacedHelpers這5個輔助和函數。

5.1 主要輔助函數

5.1.1 mapState

如果你在使用VUEX過程中使用過mapState輔助函數將state映射為計算屬性你應該會為它所支持的多樣化的映射形式感到驚訝。我們不妨先來看看官方文檔對它的介紹:

如果你深入思考過你可能會有疑問:VUEX的mapState是如何實現這么多種映射的呢?如果你現在還不明白,那么跟隨我們來一起看看吧!

mapState輔助函數定義在VUEX源碼中的790 ~ 815 行,主要是對多種映射方式以及帶命名空間的模塊提供了支持,我們來看看它的源碼:

var mapState = normalizeNamespace(function (namespace, states) {var res = {};normalizeMap(states).forEach(function (ref) {var key = ref.key;var val = ref.val;res[key] = function mappedState () {var state = this.$store.state;var getters = this.$store.getters;if (namespace) {var module = getModuleByNamespace(this.$store, 'mapState', namespace);if (!module) {return}state = module.context.state;getters = module.context.getters;}return typeof val === 'function'? val.call(this, state, getters): state[val]};// mark vuex getter for devtoolsres[key].vuex = true;});return res });

可以看到,mapState函數實際上是以函數表達式的形式的形式定義的,它的實際函數是normalizeNamespace函數,這個函數會對mapState函數的輸入參數進行歸一化/規范化處理,其最主要的功能是實現了支持帶命名空間的模塊,我們來看一下它的實現:

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

可以看到mapState實際上是中間的那段函數:

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

它實際接收namespace, map可以接收兩個參數,也可以只接受一個map參數。

  • 當用戶只提供了一個map參數時。這種情況由于類型不是string,會直接將其作為map,并將namespace置為空字符串。這種情況其實就和官方文檔的使用方式相匹配。我們可以看看官方文檔的示例就是只提供了一個map參數,并沒有提供namespace參數。
  • 當用戶提供了namespace, map兩個參數時,但namespace不是字符串類型。實際上這種情況會和情況1做一樣的處理,傳進去的第一個參數作為實際的map,第二個參數會被忽略。
  • 當用戶提供了namespace, map兩個參數時,且namespace是字符串類型。這種情況下會根據字符串最末一位字符串是否是反斜線(/)來區別對待。最終程序內部會將namespace統一處理成最后一位是反斜線(/)的字符串。
  • 由以上分析我們可以知道,上述官方文檔在此處的示例其實并不完善,該實例并沒有指出可以通過提供模塊名稱作為mapState的第一個參數來映射帶命名空間的模塊的state

    我們舉個例子看一下:

    const moduleA = {namespaced: true,//帶命名空間state: { count1: 1, age1: 20 } } const store = new Vuex.Store({state() {return {count: 0, age: 0}},modules: {a: moduleA} }) var vm = new Vue({el: '#example',store,computed: Vuex.mapState('a', {// 映射時提供模塊名作為第一個參數count1: state => state.count1,age1: state => state.age1,}) }) console.log(vm)

    其輸出如下:

    傳遞模塊名稱后,我們只能映射帶命名空間的該模塊的state,如果該模塊不帶命名空間(即沒有設置namespace屬性)、或者對于其它名字的模塊,我們是不能映射他們的state的。

    傳遞了模塊名稱,但該模塊不帶命名空間,嘗試對其進行映射:

    const moduleA = {// namespaced: true,state: { count1: 1, age1: 20 } } const store = new Vuex.Store({state() {return {count: 0, age: 0}},modules: {a: moduleA} }) var vm = new Vue({el: '#example',store,computed: Vuex.mapState('a', {count1: state => state.count1,age1: state => state.age1,}) }) console.log(vm)

    傳遞了模塊名稱,但嘗試映射其它模塊的state:

    const moduleA = {namespaced: true,state: { count1: 1, age1: 20 } } const store = new Vuex.Store({state() {return {count: 0, age: 0}},modules: {a: moduleA} }) var vm = new Vue({el: '#example',store,computed: Vuex.mapState('a', {count1: state => state.count,age1: state => state.age,}) }) console.log(vm)

    這兩種情況下的輸出結果都會是undefined:

    講完了mapState的參數,我們接著回過頭來看看mapState的實現。這里重復粘貼一下前面有關mapState定義的代碼:

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

    我們可以看到,在歸一化/規范化輸入參數后,mapState函數實際上是返回了另外一個函數的執行結果:

    return fn(namespace, map)

    這個fn就是以函數表達式定義mapState函數時的normalizeNamespace 函數的參數,我們在前面已經見到過。再次粘貼其代碼以便于分析:

    function (namespace, states) {var res = {};normalizeMap(states).forEach(function (ref) {var key = ref.key;var val = ref.val;res[key] = function mappedState () {var state = this.$store.state;var getters = this.$store.getters;if (namespace) {var module = getModuleByNamespace(this.$store, 'mapState', namespace);if (!module) {return}state = module.context.state;getters = module.context.getters;}return typeof val === 'function'? val.call(this, state, getters): state[val]};// mark vuex getter for devtoolsres[key].vuex = true;});return res };

    粗略來看,這個函數會重新定義map對象的key-value對,并作為一個新的對象返回。我們來進一步具體分析一下。

    該函數首先調用normalizeMap函數對state參數進行歸一化/規范化。normalizeMap函數定義在VUEX源碼的899 ~ 903行,我們來具體看看它的實現:

    function normalizeMap (map) {return Array.isArray(map)? map.map(function (key) { return ({ key: key, val: key }); }): Object.keys(map).map(function (key) { return ({ key: key, val: map[key] }); }) }

    該函數實際上意味著mapState函數的map參數同時支持數組和對象兩種形式。

  • 如果是數組,則會遍歷數組元素,將數組元素轉成{value: value}對象。
  • 如果是對象,則會遍歷對象key,以key-value構成{key: value}對象。
  • 這兩種形式最終都會得到一個新數組,而數組元素就是{key: value}形式的對象。

    這也與官方文檔的描述相印證,官方文檔的既提供了mapState函數的map參數是對象的例子,也提供了參數是數組的例子。

    回過頭來看,normalizeMap(states)函數執行完后會遍歷,針對每一個對象元素的value做進一步的處理。它首先拿的是根實例上掛載的store模塊的state:

    var state = this.$store.state; var getters = this.$store.getters;

    而如果mapState函數提供了命名空間參數(即模塊名),則會拿帶命名空間模塊的state:

    if (namespace) {var module = getModuleByNamespace(this.$store, 'mapState', namespace);if (!module) {return}state = module.context.state;getters = module.context.getters; }

    這其中會調用一個從根store開始,向下查找對應命名空間模塊的方法getModuleByNamespace,它定義在VUEX源碼的917 ~ 923 行:

    function getModuleByNamespace (store, helper, namespace) {var module = store._modulesNamespaceMap[namespace];if ("development" !== 'production' && !module) {console.error(("[vuex] module namespace not found in " + helper + "(): " + namespace));}return module }

    因為我們在實例化Store類的時候已經把所有模塊以namespace的為key的形式掛載在了根store實例的_modulesNamespaceMap屬性上,所以這個查詢過程只是一個對象key的查找過程,實現起來比較簡單。

    回過頭來繼續看mapState函數中“normalizeMap(states)函數執行完后會遍歷,針對每一個對象元素的value做進一步的處理”的最后的執行,它會根據原始的value是否是function而進一步處理:

  • 如果是不是function,則直接拿對應模塊的state中key對應的value。
  • 如果是function,那么將會執行該function,并且會將state, getters分別暴露給該function作為第一個和第二個參數。
  • 第二種情況在前述官方文檔的例子中也有所體現:

    // 為了能夠使用 `this` 獲取局部狀態,必須使用常規函數 countPlusLocalState (state) {return state.count + this.localCount }

    但這個官方文檔例子并不完整,它并沒有體現出還會暴露出getters參數,實際上,上述例子的完整形式應該是這樣子的:

    // 為了能夠使用 `this` 獲取局部狀態,必須使用常規函數 countPlusLocalState (state, getters) {return state.count + this.localCount + getters.somegetter }

    5.1.2 mapMutations

    與mapState可以映射模塊的state為計算屬性類似,mapMutations也可以將模塊的mutations映射為methods,我們來看看官方文檔的介紹:

    import { mapMutations } from 'vuex'export default {// ...methods: {...mapMutations([// 將 `this.increment()` 映射為 `this.$store.commit('increment')`'increment',// `mapMutations` 也支持載荷:// 將 `this.incrementBy(amount)` 映射為 `this.$store.commit('incrementBy', amount)`'incrementBy' ]),...mapMutations({add: 'increment' // 將 `this.add()` 映射為 `this.$store.commit('increment')`})} }

    同樣我們來看看它是如何實現的,它的實現定義在VUEX源碼中的817 ~ 841 行:

    var mapMutations = normalizeNamespace(function (namespace, mutations) {var res = {};normalizeMap(mutations).forEach(function (ref) {var key = ref.key;var val = ref.val;res[key] = function mappedMutation () {var args = [], len = arguments.length;while ( len-- ) args[ len ] = arguments[ len ];var commit = this.$store.commit;if (namespace) {var module = getModuleByNamespace(this.$store, 'mapMutations', namespace);if (!module) {return}commit = module.context.commit;}return typeof val === 'function'? val.apply(this, [commit].concat(args)): commit.apply(this.$store, [val].concat(args))};});return res });

    和mapState的實現幾乎完全一樣,唯一的差別只有兩點:

  • 提交mutaion時可以傳遞載荷,所以這里有一步是拷貝載荷。
  • mutation是用來提交的,所以這里拿的是commit。
  • 我們來具體分析一下代碼的執行:

    首先是拷貝載荷:

    var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ];

    然后是拿commit,如果mapMutations函數提供了命名空間參數(即模塊名),則會拿帶命名空間模塊的commit:

    var commit = this.$store.commit; if (namespace) {var module = getModuleByNamespace(this.$store, 'mapMutations', namespace);if (!module) {return}commit = module.context.commit; }

    最后則會看對應mutation的value是不是函數:

  • 如果不是函數,則直接執行commit,參數是value和載荷組成的數組。
  • 如果是函數,則直接執行該函數,并將comit作為其第一個參數,arg仍然作為后續參數。
  • 也就是說,官方文檔例子并不完整,它并沒有體現第二種情況,實際上,官方文檔例子的完整形式還應當包括:

    import { mapMutations } from 'vuex'export default {// ...methods: {...mapMutations('moduleName', {addAlias: function(commit, playload) {//將 `this.addAlias()` 映射為 `this.$store.commit('increment', amount)`commit('increment') //將 `this.addAlias(playload)` 映射為 `this.$store.commit('increment', playload)`commit('increment', playload)}})} }

    同樣,mapMutations上述映射方式都支持傳遞一個模塊名作為命名空間參數,這個在官方文檔也沒有體現:

    import { mapMutations } from 'vuex'export default {// ...methods: {...mapMutations('moduleName', [// 將 `this.increment()` 映射為 `this.$store.commit('increment')`'increment',// `mapMutations` 也支持載荷:// 將 `this.incrementBy(amount)` 映射為 `this.$store.commit('incrementBy', amount)`'incrementBy' ]),...mapMutations('moduleName', {// 將 `this.add()` 映射為 `this.$store.commit('increment')`add: 'increment' }),...mapMutations('moduleName', {addAlias: function(commit) {//將 `this.addAlias()` 映射為 `this.$store.commit('increment')`commit('increment') }})} }

    我們可以舉個例子證明一下:

    const moduleA = {namespaced: true,state: { source: 'moduleA' },mutations: {increment (state, playload) {// 這里的 `state` 對象是模塊的局部狀態state.source += playload}} } const store = new Vuex.Store({state() {return {source: 'root'}},mutations: {increment (state, playload) {state.source += playload}},modules: {a: moduleA} }) var vm = new Vue({el: '#example',store,mounted() {console.log(this.source)this.localeincrement('testdata')console.log(this.source)},computed: Vuex.mapState(['source']),methods: {...Vuex.mapMutations({localeincrement (commit, args) {commit('increment', args)}})} })

    輸出結果:

    root test.html:139 roottestdata

    另外一個例子:

    const moduleA = {namespaced: true,state: { source: 'moduleA' },mutations: {increment (state, playload) {// 這里的 `state` 對象是模塊的局部狀態state.source += playload}} } const store = new Vuex.Store({state() {return {source: 'root'}},mutations: {increment (state, playload) {state.source += playload}},modules: {a: moduleA} }) var vm = new Vue({el: '#example',store,mounted() {console.log(this.source)this.localeincrement('testdata')console.log(this.source)},computed: Vuex.mapState('a', ['source']),methods: {...Vuex.mapMutations('a', {localeincrement (commit, args) {commit('increment', args)}})} })

    輸出結果:

    moduleA test.html:139 moduleAtestdata

    5.1.3 mapGetters

    與mapState可以映射模塊的state為計算屬性類似,mapGetters也可以將模塊的getters映射為計算屬性,我們來看看官方文檔的介紹:

    mapGetters輔助函數定義在VUEX源碼中的843 ~ 864 行,我們來看看它的源碼:

    var mapGetters = normalizeNamespace(function (namespace, getters) {var res = {};normalizeMap(getters).forEach(function (ref) {var key = ref.key;var val = ref.val;val = namespace + val;res[key] = function mappedGetter () {if (namespace && !getModuleByNamespace(this.$store, 'mapGetters', namespace)) {return}if ("development" !== 'production' && !(val in this.$store.getters)) {console.error(("[vuex] unknown getter: " + val));return}return this.$store.getters[val]};// mark vuex getter for devtoolsres[key].vuex = true;});return res });

    和mapState的實現幾乎完全一樣,唯一的差別只有1點:就是最后不會出現value為函數的情況。直接拿的是對應模塊上的getters:

    return this.$store.getters[val]

    5.1.4 mapActions

    與mapMutations可以映射模塊的mutation為methods類似,mapActions也可以將模塊的actions映射為methods,我們來看看官方文檔的介紹:

    import { mapActions } from 'vuex'export default {// ...methods: {...mapActions([// 將 `this.increment()` 映射為 `this.$store.dispatch('increment')`'increment',// `mapActions` 也支持載荷:// 將 `this.incrementBy(amount)` 映射為 `this.$store.dispatch('incrementBy', amount)`'incrementBy' ]),...mapActions({// 將 `this.add()` 映射為 `this.$store.dispatch('increment')`add: 'increment' })} }

    同樣我們來看看它是如何實現的,它的實現定義在VUEX源碼中的866 ~ 890 行:

    var mapActions = normalizeNamespace(function (namespace, actions) {var res = {};normalizeMap(actions).forEach(function (ref) {var key = ref.key;var val = ref.val;res[key] = function mappedAction () {var args = [], len = arguments.length;while ( len-- ) args[ len ] = arguments[ len ];var dispatch = this.$store.dispatch;if (namespace) {var module = getModuleByNamespace(this.$store, 'mapActions', namespace);if (!module) {return}dispatch = module.context.dispatch;}return typeof val === 'function'? val.apply(this, [dispatch].concat(args)): dispatch.apply(this.$store, [val].concat(args))};});return res });

    和mapMutations的實現幾乎完全一樣,唯一的差別只有1點:

  • action是用來分派的,所以這里拿的是dispatch。
  • 我們來具體分析一下代碼的執行:

    首先是拷貝載荷:

    var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ];

    然后是拿dispatch,如果mapActions函數提供了命名空間參數(即模塊名),則會拿帶命名空間模塊的dispatch:

    var dispatch = this.$store.dispatch; if (namespace) {var module = getModuleByNamespace(this.$store, 'mapActions', namespace);if (!module) {return}dispatch = module.context.dispatch; }

    最后則會看對應action的value是不是函數:

  • 如果不是函數,則直接執行dispatch,參數是value和載荷組成的數組。
  • 如果是函數,則直接執行該函數,并將dispatch作為其第一個參數,arg仍然作為后續參數。
  • 也就是說,官方文檔例子并不完整,它并沒有體現第二種情況,實際上,官方文檔例子的完整形式還應當包括:

    import { mapActions } from 'vuex'export default {// ...methods: {...mapActions ('moduleName', {addAlias: function(dispatch, playload) {//將 `this.addAlias()` 映射為 `this.$store.dispatch('increment', amount)`dispatch('increment') //將 `this.addAlias(playload)` 映射為 `this.$store.dispatch('increment', playload)`dispatch('increment', playload)}})} }

    同樣,mapActions上述映射方式都支持傳遞一個模塊名作為命名空間參數,這個在官方文檔也沒有體現:

    import { mapActions } from 'vuex'export default {// ...methods: {...mapActions('moduleName', [// 將 `this.increment()` 映射為 `this.$store.dispatch('increment')`'increment', // `mapActions` 也支持載荷:// 將 `this.incrementBy(amount)` 映射為 `this.$store.dispatch('incrementBy', amount)`'incrementBy' ]),...mapActions('moduleName', {// 將 `this.add()` 映射為 `this.$store.dispatch('increment')`add: 'increment' }),...mapActions('moduleName', {addAlias: function (dispatch) {// 將 `this.addAlias()` 映射為 `this.$store.dispatch('increment')`dispatch('increment') }})} }

    我們可以舉個例子證明一下:

    const moduleA = {namespaced: true,state: { source: 'moduleA' },mutations: {increment (state, playload) {// 這里的 `state` 對象是模塊的局部狀態state.source += playload}},actions: {increment (context) {context.commit('increment', 'testdata')}} } const store = new Vuex.Store({state() {return {source: 'root'}},mutations: {increment (state, playload) {state.source += playload}},actions: {increment (context) {context.commit('increment', 'testdata')}},modules: {a: moduleA} }) var vm = new Vue({el: '#example',store,mounted() {console.log(this.source)this.localeincrement()console.log(this.source)},computed: Vuex.mapState(['source']),methods: {...Vuex.mapActions( {localeincrement (dispatch) {dispatch('increment')}})} })

    輸出結果:

    root roottestdata

    另外一個例子:

    const moduleA = {namespaced: true,state: { source: 'moduleA' },mutations: {increment (state, playload) {// 這里的 `state` 對象是模塊的局部狀態state.source += playload}},actions: {increment (context) {context.commit('increment', 'testdata')}} } const store = new Vuex.Store({state() {return {source: 'root'}},mutations: {increment (state, playload) {state.source += playload}},actions: {increment (context) {context.commit('increment', 'testdata')}},modules: {a: moduleA} }) var vm = new Vue({el: '#example',store,mounted() {console.log(this.source)this.localeincrement()console.log(this.source)},computed: Vuex.mapState('a', ['source']),methods: {...Vuex.mapActions('a', {localeincrement (dispatch) {dispatch('increment')}})} })

    輸出結果:

    moduleA moduleAtestdata

    5.1.5 createNamespacedHelpers

    createNamespacedHelpers主要是根據傳遞的命名空間產生對應模塊的局部化mapState、mapGetters、mapMutations、mapActions映射函數,它定義在VUEX源碼的892 ~ 897行:

    var createNamespacedHelpers = function (namespace) { return ({mapState: mapState.bind(null, namespace),mapGetters: mapGetters.bind(null, namespace),mapMutations: mapMutations.bind(null, namespace),mapActions: mapActions.bind(null, namespace) }); };

    5.2 其它輔助函數

    5.2.1 isObject

    isObject定義在VUEX源碼的94 ~ 96 行,主要判斷目標是否是有效對象,其實現比較簡單:

    //判斷是不是object function isObject (obj) {return obj !== null && typeof obj === 'object' }

    5.2.2 isPromise

    isPromise定義在VUEX源碼的98 ~ 100 行,主要判斷目標是否是promise,其實現比較簡單:

    function isPromise (val) {return val && typeof val.then === 'function' }

    5.2.3 assert

    assert定義在VUEX源碼的102 ~ 104 行,主要用來斷言,其實現比較簡單:

    function assert (condition, msg) {if (!condition) { throw new Error(("[vuex] " + msg)) } }

    第六章 總結

    到此這本VUEX學習筆記算是寫完了,總體而言是對個人在學習VUEX源碼過程中的理解、想法進行的記錄和總結,這其中除了不可避免的主觀視角外,自然還會存在一些理解上的偏差甚至錯誤,希望看到這本書的人能夠指正。

    更多內容可查看本人博客以及github

    總結

    以上是生活随笔為你收集整理的VUEX源码学习笔记(第5~6章 共6章)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。