摘要:至此它便作為一個唯一數據源而存在。改變中的狀態的唯一途徑就是顯式地提交。這樣使得可以方便地跟蹤每一個狀態的變化,從而能夠實現一些工具幫助更好地了解應用背后的基本思想,借鑒了和參考源碼解讀一架構入門教程狀態管理
準備工作 1.下載安裝運行
這里以2.0.0-rc.6為例
官網github下載鏈接(對應版本):https://github.com/vuejs/vuex...
點擊下載到本地
按照readme.md
$ npm install $ npm run dev # serve examples at localhost:80802.example示例
查看package.json dev命令,
"dev": "node examples/server.js"
打開http://localhost:8080/
examples里面的文件都能運行了,
Counter,
Counter with Hot Reload,
Shopping Cart,
TodoMVC,
FluxChat 這些每一個有很有代表性
vuex 只暴露出了6個方法
export default { Store, install, mapState, mapMutations, mapGetters, mapActions }1.install方法
install函數用于vue
vue.use() 里會調用自動install方法
function install (_Vue) { if (Vue) { console.error( "[vuex] already installed. Vue.use(Vuex) should be called only once." ) return } Vue = _Vue applyMixin(Vue) } applyMixin(Vue){ // ... function vuexInit () { const options = this.$options // store injection if (options.store) { this.$store = options.store } else if (options.parent && options.parent.$store) { this.$store = options.parent.$store } } }
new Vue({store})傳進去一個store對象,在new Vue()的時候對options進行了處理
vm.$options.store = store // 把傳入的對象除了$el都掛在了$options下 // 參考vue源碼對options處理部分
這樣做
if (options.store) { // 這樣在 vue 里全局里通過 $store能夠訪問store對象 this.$store = options.store }2.Store構造函數
class Store { constructor (options = {}) {assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`) assert(typeof Promise !== "undefined", `vuex requires a Promise polyfill in this browser.`) // options解構賦值 const { state = {}, plugins = [], strict = false } = options // store internal state this._options = options this._committing = false this._actions = Object.create(null) this._mutations = Object.create(null) this._wrappedGetters = Object.create(null) this._runtimeModules = Object.create(null) this._subscribers = [] this._watcherVM = new Vue() // bind commit and dispatch to self const store = this // dispatch, commit 解構賦值 const { dispatch, commit } = this // dispatch改變this,使this為store this.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) } // 嚴格模式 // strict mode this.strict = strict // 對傳入module的處理 // init root module. // this also recursively registers all sub-modules // and collects all module getters inside this._wrappedGetters installModule(this, state, [], options) // initialize the store vm, which is responsible for the reactivity // (also registers _wrappedGetters as computed properties) resetStoreVM(this, state) // apply plugins plugins.concat(devtoolPlugin).forEach(plugin => plugin(this)) } }
get state () { //返回state,state掛在在stare.vm下 return this._vm.state } set state (v) { //不能被外部改變,私有變量 assert(false, `Use store.replaceState() to explicit replace store state.`) }3.installModule函數
installModule(this, state, [], options)
function installModule (store, rootState, path, module, hot) { // 如果path為[]那么isRoot為tree const isRoot = !path.length // 把傳入的options請求解構賦值, const { state, actions, mutations, getters, modules } = module // hot不存在而且path不為[] // set state if (!isRoot && !hot) { const parentState = getNestedState(rootState, path.slice(0, -1)) const moduleName = path[path.length - 1] store._withCommit(() => { Vue.set(parentState, moduleName, state || {}) }) } if (mutations) { Object.keys(mutations).forEach(key => { registerMutation(store, key, mutations[key], path) }) } if (actions) { Object.keys(actions).forEach(key => { registerAction(store, key, actions[key], path) }) } if (getters) { wrapGetters(store, getters, path) } if (modules) { //注意 只有傳遞了modules 才會執行if (!isRoot && !hot)后面的處理,這里對path.concat(key)進行了處理 Object.keys(modules).forEach(key => { installModule(store, rootState, path.concat(key), modules[key], hot) }) } }
這里有個很重要的概念要理解,什么是 path. vuex 的一個 store 實例可以拆分成很多個 module ,不同的 module 可以理解成一個子代的 store 實例(事實上,module 確實和 store 具有一樣的結構),這是一種模塊化的概念。因此這里的 path 可以理解成是表示一種層級關系(樹狀),當你有了一個 root state 之后,根據這個 root state 和 path 可以找到 path 路徑對應的一個 local state, 每一個 module 下的 mutations 和 actions 改變的都是這個local state,而不是 root state.
看一下 getNestedState方法
/* * state: Object, path: Array * 假設path = ["a", "b", "c"] * 函數返回結果是state[a][b][c] */ function getNestedState (state, path) { return path.length ? path.reduce(function (state, key) { return state[key]; }, state) : state }
這里學習一下reduce的妙用 reduce不清楚的 mdn傳送門點我
/** handler是傳入的mutation[key]方法比如 increment (state) { state.count++ }, decrement (state) { state.count-- } type就是key increment, decrement **/ registerMutation函數 ---------------- function registerMutation (store, type, handler, path = []) { const entry = store._mutations[type] || (store._mutations[type] = []) entry.push(function wrappedMutationHandler (payload) { handler(getNestedState(store.state, path), payload) }) }
所有的 mutations 都經過處理后,保存在了 store._mutations 對象里。 _mutations 的結構為
_mutations: { increment: [function wrappedMutationHandler(payload){increment(store.state.modules){...}}, ...], decrement: [function wrappedMutationHandler(payload){decrement(store.state.modules, payload){...}}, ...], ... }
這個對應第一個參數為該模塊下的state對象,如果沒有modules就是rootState,
第二個參數就是payload 用戶自定義傳遞的參數
increment (state) state.count++ }, decrement (state) { state.count-- }
同理
4.registerAction函數function registerAction (store, type, handler, path = []) { const entry = store._actions[type] || (store._actions[type] = []) const { dispatch, commit } = store entry.push(function wrappedActionHandler (payload, cb) { let res = handler({ dispatch, commit, getters: store.getters, state: getNestedState(store.state, path), rootState: store.state }, payload, cb) if (!isPromise(res)) { // 返回 Promise res = Promise.resolve(res) } if (store._devtoolHook) { return res.catch(err => { store._devtoolHook.emit("vuex:error", err) throw err }) } else { return res } }) }
被處理成
_actions: { increment: [function wrappedActionHandler(payload){increment({ dispatch, commit, getters: store.getters, state: getNestedState(store.state, path), rootState: store.state }){...}}, ...], decrement: [function wrappedActionHandler(payload){decrement({ dispatch, commit, getters: store.getters, state: getNestedState(store.state, path), rootState: store.state }, payload){...}}, ...], ... }
第一個參數傳入一個對象,所以才能使用commit方法
const actions = { increment: ({ commit }) => commit("increment"), decrement: ({ commit }) => commit("decrement") }5.commit和dispatch
commit為同步,dispatch為異步,當所有執行完畢返回Promise
commit (type, payload, options) { // check object-style commit if (isObject(type) && type.type) { options = payload payload = type type = type.type } const mutation = { type, payload } const entry = this._mutations[type] if (!entry) { console.error(`[vuex] unknown mutation type: ${type}`) return } this._withCommit(() => { entry.forEach(function commitIterator (handler) { handler(payload) }) }) if (!options || !options.silent) { this._subscribers.forEach(sub => sub(mutation, this.state)) } } dispatch (type, payload) { // check object-style dispatch if (isObject(type) && type.type) { payload = type type = type.type } const entry = this._actions[type] if (!entry) { console.error(`[vuex] unknown action type: ${type}`) return } return entry.length > 1 ? Promise.all(entry.map(handler => handler(payload))) : entry[0](payload) }6.wrapGetters
// store增加一個 _wrappedGetters 屬性 function wrapGetters (store, moduleGetters, modulePath) { Object.keys(moduleGetters).forEach(getterKey => { const rawGetter = moduleGetters[getterKey] if (store._wrappedGetters[getterKey]) { console.error(`[vuex] duplicate getter key: ${getterKey}`) return } store._wrappedGetters[getterKey] = function wrappedGetter (store) { return rawGetter( getNestedState(store.state, modulePath), // local state store.getters, // getters store.state // root state ) } }) }
注意 這里的所有 getters 都儲存在了全局的一個 _wrappedGetters 對象中,同樣屬性名是各個 getterKey ,屬性值同樣是一個函數,執行這個函數,將會返回原始 getter 的執行結果。
7.resetStoreVMfunction resetStoreVM (store, state) { const oldVm = store._vm //... Object.keys(wrappedGetters).forEach(key => { const fn = wrappedGetters[key] // use computed to leverage its lazy-caching mechanism computed[key] = () => fn(store) Object.defineProperty(store.getters, key, { get: () => store._vm[key] }) }) //... store._vm = new Vue({ data: { state }, computed }) if (oldVm) { // dispatch changes in all subscribed watchers // to force getter re-evaluation. store._withCommit(() => { oldVm.state = null }) Vue.nextTick(() => oldVm.$destroy()) } }
著重講一下最重要的
store._vm = new Vue({ data: { state }, computed })
state是放在vue實例對象的 this.data.state 里面的
這個 new Vue 和你平常的new Vue不一樣,這是兩個vue實例,數據不共用
之所以掛在在vue是方便使用vue的一些方法nextTick,set等,更方便控制state狀態
所以在vue組件里如果不寫
computed:{ ...mapState(["count"]) },
就不能直接寫 template 里寫{{count}} 而必須使用 this.$store.state.count
map輔助函數 normalizeMap/* * 如果map是一個數組 ["type1", "type2", ...] * 轉化為[ * { * key: type1, * val: type1 * }, * { * key: type2, * val: type2 * }, * ... * ] * 如果map是一個對象 {type1: fn1, type2: fn2, ...} * 轉化為 [ * { * key: type1, * val: fn1 * }, * { * key: type2, * val: fn2 * }, * ... * ] */ 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] }); }) }
normalizeMap 函數接受一個對象或者數組,最后都轉化成一個數組形式,數組元素是包含 key 和 value 兩個屬性的對象。
mapexport function mapState (states) { const res = {} normalizeMap(states).forEach(({ key, val }) => { res[key] = function mappedState () { return typeof val === "function" //是函數,執行返回結果 ? val.call(this, this.$store.state, this.$store.getters) : this.$store.state[val] // 不是,返回$store.state[val]值 } }) return res } export function mapMutations (mutations) { const res = {} normalizeMap(mutations).forEach(({ key, val }) => { res[key] = function mappedMutation (...args) { // 通過commit執行Mutation里的函數和自己用$store.commit()執行結果是一樣,定義一個方法,用戶使用起來更方便 return this.$store.commit.apply(this.$store, [val].concat(args)) } }) return res } //這里 getters 同樣接受一個數組,同樣返回一個對象 export function mapGetters (getters) { const res = {} normalizeMap(getters).forEach(({ key, val }) => { res[key] = function mappedGetter () { if (!(val in this.$store.getters)) { console.error(`[vuex] unknown getter: ${val}`) } return this.$store.getters[val] } }) return res } export function mapActions (actions) { const res = {} normalizeMap(actions).forEach(({ key, val }) => { res[key] = function mappedAction (...args) { // 同mapMutations return this.$store.dispatch.apply(this.$store, [val].concat(args)) } }) return res }
map* 方法,這四種方法可以都返回一個對象,所以在vue里我們能夠使用...
computed:{ ...mapState(["count"]) }小結
【單一狀態樹】
vuex 使用單一狀態樹——用一個對象就包含了全部的應用層級狀態。至此它便作為一個“唯一數據源 (SSOT)”而存在。這也意味著,每個應用將僅僅包含一個 store 實例。單一狀態樹能夠直接地定位任一特定的狀態片段,在調試的過程中也能輕易地取得整個當前應用狀態的快照
每一個 Vuex 應用的核心就是 store(倉庫)。“store”基本上就是一個容器,它包含著應用中大部分的狀態 (state)。Vuex 和單純的全局對象有以下兩點不同:
1、Vuex 的狀態存儲是響應式的。當 Vue 組件從 store 中讀取狀態的時候,若 store 中的狀態發生變化,那么相應的組件也會相應地得到高效更新
2、不能直接改變 store 中的狀態。改變 store 中的狀態的唯一途徑就是顯式地提交 (commit) mutation。這樣使得可以方便地跟蹤每一個狀態的變化,從而能夠實現一些工具幫助更好地了解應用
Vuex 背后的基本思想,借鑒了 Flux、Redux和 The Elm Architecture
參考
vuex 2.0源碼解讀(一)
Flux 架構入門教程
Vue狀態管理vuex
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/98370.html
摘要:簡單點說,當你使用構造函數,它實際上做了這么幾件事,首先定義給實例定義一些內部屬性,之后就是綁定和的上下文對象永遠是實例上,之后根據傳入的充實內部狀態等等。函數執行的結果是返回一個對象,屬性名對應于傳入的對象或者數組元素。 轉載請注明出處 https://segmentfault.com/a/11... vuex2.0 和 vuex1.x 相比,API改變的還是很多的,但基本思想沒什么...
摘要:無需使用服務器實時動態編譯,而是使用預渲染方式,在構建時簡單地生成針對特定路由的靜態文件。與可以部署在任何靜態文件服務器上的完全靜態單頁面應用程序不同,服務器渲染應用程序,需要處于運行環境。更多的服務器端負載。 目錄結構 -no-ssr-demo 未做ssr之前的項目代碼用于對比 -vuecli2ssr 將vuecli生成的項目轉為ssr -prerender-demo 使用prer...
摘要:第一篇文章我會結合和的部分源碼,來說明注入生命周期的過程。說到源碼,其實沒有想象的那么難。但是源碼的調用樹會復雜很多。應用的業務代碼逐漸復雜,事件事件總線等通信的方式的弊端就會愈發明顯。狀態管理是組件解耦的重要手段。前言 這篇文章是【前端詞典】系列文章的第 13 篇文章,接下的 9 篇我會圍繞著 Vue 展開,希望這 9 篇文章可以使大家加深對 Vue 的了解。當然這些文章的前提是默認你對 ...
摘要:學習,首先明白是什么是一個專為應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態,并以相應的規則保證狀態以一種可預測的方式發生變化。 學習vuex,首先明白vuex是什么?Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態,并以相應的規則保證狀態以一種可預測的方式發生變化。 如果你在使用 vue.js , 那么我...
摘要:個人看來,一個狀態管理的應用,無論是使用,還是,最困難的部分是在的設計。中,并沒有移除,而是改為用于觸發。也是一個對象,用于注冊,每個都是一個用于返回一部分的。接受一個數組或對象,根據相應的值將對應的綁定到組件上。 系列文章: Vue 2.0 升(cai)級(keng)之旅 Vuex — The core of Vue application (本文) 從單頁應用(SPA)到服務器...
閱讀 3096·2021-09-28 09:42
閱讀 3449·2021-09-22 15:21
閱讀 1122·2021-07-29 13:50
閱讀 3564·2019-08-30 15:56
閱讀 3367·2019-08-30 15:54
閱讀 1196·2019-08-30 13:12
閱讀 1172·2019-08-29 17:03
閱讀 1198·2019-08-29 10:59