Vue組件通信原理剖析(二)全局狀態管理Vuex

首先我們先從一個面試題入手。

面試官問: “Vue中組件通信的常用方式有哪些?”
我答:
1. props
2. 自定義事件
3. eventbus
4. vuex
5. 還有常見的邊界情況$parent、$children、$root、$refs、provide/inject
6. 此外還有一些非props特性$attrs、$listeners

面試官追問:“那你能分別說說他們的原理嗎?”
我:[一臉懵逼]😳

今天我們來看看Vuex內部的奧秘!
如果要看別的屬性原理請移步到Vue組件通信原理剖析(一)事件總線的基石 on和on和onemit和Vue組件通信原理剖析(三)provide/inject原理分析

vuex

Vuex集中式存儲管理應用的所有組件的狀態,并以相應的規則保證狀態以可預測的方式發生變化。
在這里插入圖片描述

我們先看看如何使用vuex,

  • 第一步:定義一個Store

    // store/index.js
    import Vue from 'vue'
    import Vuex from 'vuex'Vue.use(Vuex)export default = new Vuex.Store({state: {counter: 0},getters: {doubleCounter(state) {return state.counter * 2}},mutations: {add(state) {state.counter ++ }},actions: {add({commit}) {setTimeout(() => {commit('add')}, 1000);}}
    })
    
  • 第二步,掛載app

    // main.js
    import vue from 'vue'
    import App form './App.vue'
    import store from './store'new Vue({store,render: h => h(App)
    }).$mount('#app')
    
  • 第三步:狀態調用

    // test.vue
    <p @click="$store.commit('add')">counter: {{ $store.state.counter }}</p>
    <p @click="$store.dispatch('add')">async counter: {{ $store.state.counter }}</p>
    <p>double counter: {{ $store.getters.doubleCounter }}</p>
    

從上面的例子,我們可以看出,vuex需要具備這么幾個特點:

  1. 使用Vuex只需執行 Vue.use(Vuex),保證vuex是以插件的形式被vue加載。
  2. state的數據具有響應式,A組件中修改了,B組件中可用修改后的值。
  3. getters可以對state的數據做動態派生。
  4. mutations中的方法是同步修改。
  5. actions中的方法是異步修改。

那我們今天就去源碼里探索以下,vuex是怎么實現的,又是怎么解決以上的問題的!

問題1:vuex的插件加載機制

所謂插件機制,就是需要實現Install方法,并且通過mixin形式混入到Vue的生命周期中,我們先來看看Vuex的定義

  • 需要對外暴露一個對象,這樣就可以滿足new Vuex.Store()

    // src/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
    }
    
  • 其次是定義store,并且實現vue的Install方法

    // src/store.js
    let Vue // bind on installexport class Store {......
    }// 實現的Install方法 
    export function install (_Vue) {if (Vue && _Vue === Vue) {if (process.env.NODE_ENV !== 'production') {console.error('[vuex] already installed. Vue.use(Vuex) should be called only once.')}return}Vue = _VueapplyMixin(Vue)
    }
    

問題2:state的數據響應式

看懂了Vuex的入口定義,下面我們就針對store的定義來一探究竟,先看看state的實現

// src/store.js
export class Store {constructor(options = {}) {......// strict modethis.strict = strictconst state = this._modules.root.state// initialize the store vm, which is responsible for the reactivity// (also registers _wrappedGetters as computed properties)// 看上面的注釋可以得知,resetStoreVM就是初始化store中負責響應式的vm的方法,而且還注冊所有的gettersz作為vm的計算屬性resetStoreVM(this, state)}
}

我們來看看resetStoreVM的具體實現

// src/store.js
function resetStoreVM (store, state, hot) {const oldVm = store._vm// bind store public gettersstore.getters = {}// reset local getters cachestore._makeLocalGettersCache = Object.create(null)const wrappedGetters = store._wrappedGettersconst computed = {}// 這里是實現getters的派生forEachValue(wrappedGetters, (fn, key) => {// use computed to leverage its lazy-caching mechanism// direct inline function use will lead to closure preserving oldVm.// using partial to return function with only arguments preserved in closure environment.computed[key] = partial(fn, store)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// 這是是通過new一個Vue實例,并將state作為實例的datas屬性,那他自然而然就具有了響應式const silent = Vue.config.silentVue.config.silent = truestore._vm = new Vue({data: {$$state: state},computed})Vue.config.silent = silent// enable strict mode for new vmif (store.strict) {enableStrictMode(store)}if (oldVm) {if (hot) {// dispatch changes in all subscribed watchers// to force getter re-evaluation for hot reloading.store._withCommit(() => {oldVm._data.$$state = null})}Vue.nextTick(() => oldVm.$destroy())}
}

問題3:getters實現state中的數據的派生

關于getters的實現,我們在上面也做了相應的解釋,實際上就是將getters的方法包裝一層后,收集到computed對象中,并使用Object.defineProperty注冊store.getters,使得每次取值時,從store._vm中取。

關鍵的步驟就是創建一個Vue的實例

store._vm = new Vue({data: {$$state: state // 這是store中的所有state},computed // 這是store中的所有getters
})

問題4:mutations中同步commit

// src/store.js
// store的構造函數
constructor(options = {}) {// 首先在構造方法中,把store中的commit和dispatch綁定到自己的實例上,// 為什么要這么做呢?// 是因為在commit或者dispatch時,尤其是dispatch,執行function時會調用實例this,而方法體內的this是具有作用域屬性的,所以如果要保證每次this都代表store實例,就需要重新綁定一下。const store = thisconst { dispatch, commit } = thisthis.dispatch = function boundDispatch (type, payload) {return dispatch.call(store, type, payload)}this.commit = function boundCommit (type, payload, options) {return commit.call(store, type, payload, options)}
}// commit 的實現
commit (_type, _payload, _options) {// check object-style commitconst {type,payload,options} = unifyObjectStyle(_type, _payload, _options)const mutation = { type, payload }// 通過傳入的類型,查找到mutations中的對應的入口函數const entry = this._mutations[type]......// 這里是執行的主方法,通過遍歷入口函數,并傳參執行this._withCommit(() => {entry.forEach(function commitIterator (handler) {handler(payload)})})......
}

問題5:actions中的異步dispatch

上面說了在構造store時綁定dispatch的原因,下面我們就繼續看看dispatch的具體實現。

// src/store.js
// dispatch 的實現
dispatch (_type, _payload) {// check object-style dispatchconst {type,payload} = unifyObjectStyle(_type, _payload)const action = { type, payload }// 同樣的道理,通過type獲取actions中的入口函數const entry = this._actions[type]······// 由于action是異步函數的集合,這里就用到了Promise.all,來合并多個promise方法并執行const result = entry.length > 1? Promise.all(entry.map(handler => handler(payload))): entry[0](payload)return result.then(res => {try {this._actionSubscribers.filter(sub => sub.after).forEach(sub => sub.after(action, this.state))} catch (e) {if (process.env.NODE_ENV !== 'production') {console.warn(`[vuex] error in after action subscribers: `)console.error(e)}}return res})
}

到這里,我們就把整個store中狀態存儲和狀態變更的流程系統的串聯了一遍,讓我們對Vuex內部的機智有個簡單的認識,最后我們根據我們對Vuex的理解來實現一個簡單的Vuex。

// store.js
let Vue// 定義store類
class Store{constructor(options = {}) {this.$options = optionsthis._mutations = options.mutationsthis._actions = options.actionsthis._wrappedGetters = options.getters// 定義computedconst computed = {}this.getters = {}const store = thisObject.keys(this._wrappedGetters).forEach(key => {// 獲取用戶定義的gettersconst fn = store._wrappedGetters[key]// 轉換為computed可以使用無參數形式computed[key] = function() {return fn(store.state)}// 為getters定義只讀屬性Object.defineProperty(store.getters, key {get:() => store._vm[key]})})// state的響應式實現this._vm = new Vue({data: {// 加兩個$,Vue不做代理$$state: options.state},computed // 添加計算屬性})this.commit = this.commit.bind(this)this.dispatch = this.dispatch.bind(this)}// 存取器,獲取store.state ,只通過get形式獲取,而不是直接this.xxx, 達到對stateget state() {return this._vm._data.$$state}set state(v) {// 如果用戶不通過commit方式來改變state,就可以在這里做一控制}// commit的實現commit(type, payload) {const entry = this._mutations[type]if (entry) {entry(this.state, payload)}}// dispatch的實現dispatch(type, payload) {const entry = this._actions[type]if (entry) {entry(this, payload)}}  
}// 實現install
function install(_Vue) {Vue = _VueVue.mixin({beforeCreate() {if (this.$options.store) {Vue.prototype.$Store = this.$options.store // 這樣就可以使用 this.$store}}})
}// 導出Vuex對象
export default {Store,install
}

全部文章鏈接

Vue組件通信原理剖析(一)事件總線的基石 on和on和onemit
Vue組件通信原理剖析(二)全局狀態管理Vuex
Vue組件通信原理剖析(三)provide/inject原理分析

最后喜歡我的小伙伴也可以通過關注公眾號“劍指大前端”,或者掃描下方二維碼聯系到我,進行經驗交流和分享,同時我也會定期分享一些大前端干貨,讓我們的開發從此不迷路。
在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/248806.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/248806.shtml
英文地址,請注明出處:http://en.pswp.cn/news/248806.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

初識單點登錄及JWT實現

單點登錄 多系統&#xff0c;單一位置登錄&#xff0c;實現多系統同時登錄的一種技術 &#xff08;三方登錄&#xff1a;某系統使用其他系統的用戶&#xff0c;實現本系統登錄的方式。如微信登錄、支付寶登錄&#xff09; 單點登錄一般是用于互相授信的系統&#xff0c;實現單一…

Vue組件通信原理剖析(三)provide/inject原理分析

首先我們先從一個面試題入手。 面試官問&#xff1a; “Vue中組件通信的常用方式有哪些&#xff1f;” 我答&#xff1a; 1. props 2. 自定義事件 3. eventbus 4. vuex 5. 還有常見的邊界情況$parent、$children、$root、$refs、provide/inject 6. 此外還有一些非props特性$att…

iMX6開發板-uboot-網絡設置和測試

本文章基于迅為IMX6開發板 將iMX6開發板通過網線連接到路由器&#xff0c;同時連接好調試串口&#xff0c;上電立即按 enter&#xff0c;即可進入 uboot。然后輸入命令 pri&#xff0c;查看開發板當前的配置&#xff0c;如下圖所示可以看到 ip 地址、子網掩碼 等信息。 本文檔測…

Django ajax 檢測用戶名是否已被注冊

添加一個 register.html 頁面 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head> <body> <form><p>用戶名<input id"username" type&…

pyqt5控件

背景色設置 self.tab.setStyleSheet("background: rgb(238, 233, 233)") self.but_0.setStyleSheet("background: rgb(0, 255, 255)")樣式&#xff1a; self.but_0.setStyle(QStyleFactory.create("Windows"))字體&#xff1a; self.lineEdit.se…

詳解JDBC連接數據庫

一、概念 1. 為了能讓程序操作數據庫&#xff0c;對數據庫中的表進行操作&#xff0c;每一種數據庫都會提供一套連接和操作該數據庫的驅動&#xff0c;而且每種數據庫的驅動都各不相同&#xff0c;例如mysql數據庫使用mysql驅動&#xff0c;oracle數據庫使用oracle驅動&#xf…

ASP.NET MVC 自定義模型綁定1 - 自動把以英文逗號分隔的 ID 字符串綁定成 Listint...

直接貼代碼了&#xff1a; CommaSeparatedModelBinder.cs using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Web.Mvc;namespace MvcSample.Extensions {public class CommaSeparatedMode…

ZOJ4024 Peak

題意 給出一個數組 判斷這個數組是否形成了一個“山峰” 即中間有個數最大 從第一個數到這個數遞增 從這個數到最后一個數遞減 模擬 從兩端分別以遞增和遞減判斷 看第一個不滿足遞增或遞減的數是否相等并且沒越界就可以了 AC代碼&#xff1a; 1 #include<bits/stdc.h>2 u…

基本數據類型與String之間的轉換

字符串轉基本數據類型 調用基本數據類型對應的包裝類中的方法parseXXX(String)或valueOf(String)即可返回相應基本類型。 基本數據類型轉字符串 一種方法是將基本數據類型與空字符串&#xff08;""&#xff09;連接&#xff08;&#xff09;即可獲得其所對應的字符串…

springmvc跨域問題

1、跨域問題&#xff1a; 按照網上所有的方法試了一遍&#xff0c;都沒跨過去&#xff0c;正在無助之際&#xff0c;使用filter按照下面的方法解決的時候出現了轉機&#xff1a; 添加filter&#xff1a; package com.thc.bpm.filter;import javax.servlet.*; import javax.serv…

柳傳志給年輕人的建議:比起過日子,更要奔日子

改革開放的 40 年&#xff0c;是柳傳志實現人生價值的 40 年。 十一屆三中全會后&#xff0c;伴隨“科學的春天”&#xff0c;迎著改革開放的大潮&#xff0c;柳傳志“下海”了。但他并沒想到&#xff0c;自己選擇的電腦行業&#xff0c;讓他和聯想集團站在了潮頭。 從 1984 年…

成功秀了一波scala spark ML邏輯斯蒂回歸

1、直接上官方代碼&#xff0c;調整過的&#xff0c;方可使用 package com.test import org.apache.spark.{SparkConf, SparkContext} import org.apache.spark.mllib.classification.{LogisticRegressionModel, LogisticRegressionWithLBFGS} import org.apache.spark.mllib.e…

記錄一次查詢log的經歷

一大早發現生產數據庫的基礎資料被刪除。 由于每天都做了差異備份&#xff0c;而且是基礎資料&#xff0c;這樣數據就不會擔心找不回來。 首先通過每天的差異本分文件進行查看數據丟失的大概時間&#xff0c;查到數據丟失是在17晚上備份過后18丟失的。 然后找18號的數據庫執行記…

移動端輪播圖

1. 頁面布局 1.1 頁面框架 <body><div class"box"><div class"tupian"><img src"4.webp" alt""><img src"1.webp" alt""><img src"2.webp" alt""><…

Boost 序列化

原文鏈接&#xff1a; https://blog.csdn.net/qq2399431200/article/details/45621921 1. 編譯器 gcc, boost 1.55 2.1第一個簡單的例子 —— Hello World &#xff0c;將字符串內容歸檔到文本文件中 #include <iostream>#include <fstream>#include <string>…

docker CE 的安裝

一、Docker CE的安裝1.先決條件運行環境&#xff1a;Ubuntu 64位或者其他支持Docker的64位系統運行配置&#xff0c;linux內核版本必須大于 3.10&#xff0c;否則會因為缺少容器運行所需的功能而出錯。 2.在ubuntu下安裝Docker CEUbuntu版本? Cosmic 18.10 ? Bionic 18.04 (…

nodeJS中的異步編程

nodejs 不是單線程 在博客項目中關于異步問題&#xff1a; 1.當用戶添加一條博客時 需要通過post方式向服務器發送數據 后臺獲取用戶以post方式拿到傳送過來的數據 然后存入數據庫&#xff1a; 上面的代碼&#xff1a;創建一個空字符串 當用戶向服務器發送請求時出發data事件將…

day01筆記

linux基本命令的學習&#xff1a; 1.查看主機名hostname 2.修改主機名hostnamectl set-hostname s16ds 3.linux命令提示符 [roots16ds ~]# # 超級用戶的身份提示符 $ 普通用戶的身份提示符4.修改命令提示符 PS1變量控制 [roots16ds ~]# echo $PS1 [\u\h \W]\$PS1[\u\h \w \t]…

angular 路由

1. vscode編輯器快速新建主路由&#xff1a; ng-router注意修改為 根路由為&#xff1a;‘forRoot()’app-route.module.ts;{ path:,redirectTo:/login,pathMatch:full } 當路由為空的時候&#xff0c;會重定向到/login路由&#xff0c;必須加上pathMatch:full 1 import { Rou…

nodeJs 操作數據庫

首先在node中下載mysql包 npm install mysql 連接數據庫 var mysql require(mysql); var con mysql.createConnection({host : localhost,user : root,password : root,database : blog });開啟鏈接 con.connect();執行增刪改查 不同功能創建不同的sql語句即可…