Hello,各位小伙伴,接下來的一段時間里,我會把我的課程《Vue.js 3.0 核心源碼解析》中問題的答案陸續在我的公眾號發布,由于課程的問題大多數都是開放性的問題,所以我的答案也不一定是標準的,僅供你參考喔。
本期的問題:如果你想利用依賴注入讓整個應用下組件都能共享某個數據,你會怎么做?為什么?
這個問題本身并不難,因為你只要知道了依賴注入的實現原理,你就可以輕松回答出:只要在應用的根實例上 provide
某個數據,然后在子組件 inject
使用,就相當于整個應用的組件共享該數據了。
看上去,使用 provide/inject
就可以實現全局數據共享,這個能力似乎和 Vuex
提供的能力類似,那么它可以替代 Vuex
嗎?
Vuex 的核心概念
Vuex
是什么,官方的解釋是一個專為 Vue.js 應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態,并以相應的規則保證狀態以一種可預測的方式發生變化。
Vuex
本質上是一種全局單例模式的方式來管理組件的共享狀態。在這種模式下,我們的組件樹構成了一個巨大的“視圖”,不管在樹的哪個位置,任何組件都能獲取狀態或者觸發行為。
在 Vuex
中,有四個核心的概念,我們來簡單過一下。
State
state
是 Vuex
中最基礎的概念,它用于數據的 存儲,舉個例子:
import?{?createStore?}?from?'vuex'
const?store?=?createStore({state:?{todos:?[{?id:?1,?text:?'...',?done:?true?},{?id:?2,?text:?'...',?done:?false?}]}
})
我們可以通過 store.state.todos
來訪問到其中的 todos
數據。
Getter
有些時候,我們希望獲取的數據可能不是單一的在 state
中的數據,可能需要做一些邏輯運算,我們可以使用 getter
,它就是 store
的計算屬性。延續前一個例子:
import?{?createStore?}?from?'vuex'
const?store?=?createStore({state:?{todos:?[{?id:?1,?text:?'...',?done:?true?},{?id:?2,?text:?'...',?done:?false?}]},getters:?{doneTodos:?state?=>?{return?state.todos.filter(todo?=>?todo.done)}}
})
我們可以通過 store.getters.doneTodos
訪問到所有已完成的 todos
數據。
Mutation
數據有讀就會有寫,為了確保數據的改變可追蹤,更改 state
數據的唯一的方式是提交 mutation
。延續前一個例子:
import?{?createStore?}?from?'vuex'
const?store?=?createStore({state:?{todos:?[{?id:?1,?text:?'...',?done:?true?},{?id:?2,?text:?'...',?done:?false?}]},getters:?{doneTodos:?state?=>?{return?state.todos.filter(todo?=>?todo.done)}},mutations:?{finishToDo(state,?index)?{state.todos[index].done?=?true}}
})
我們可以通過 store.commit('finishTodo', 1)
來修改第二個 todo
的完成狀態。
Action
action
類似 mutation
,不同在于在 action
內部并不直接修改數據,還是通過提交 mutation
來更改數據,此外 action
內部還能包含任意的異步操作。延續前一個例子:
import?{?createStore?}?from?'vuex'
const?store?=?createStore({state:?{todos:?[{?id:?1,?text:?'...',?done:?true?},{?id:?2,?text:?'...',?done:?false?}]},getters:?{doneTodos:?state?=>?{return?state.todos.filter(todo?=>?todo.done)}},mutations:?{finishToDo(state,?index)?{state.todos[index].done?=?true}},actions:?{delayFinishTodo({commit},?index)?{setTimeout(()?=>?{commit('finishToDo',?index)},?1000)}}
})
我們可以通過 store.dispatch('delayFinishTodo', 1)
延時 1s
后修改第二個 todo
的完成狀態。
至此,我們了解了 Vuex
的四個最核心的概念,目前為止,我們都是通過原生 JavaScript 去操作 store
實例,并沒有和組件關聯,那么我們如何在組件中訪問到 store
實例呢?
在組件中訪問 store
在 Vue.js 3.0 中,我們通過 createStore
創建了 store
實例后,會在創建 App
對象的時候注入進去。
import?{?createApp?}?from?'vue'
import?App?from?'./App.vue'
import?store?from?'./store'createApp(App).use(store).mount('#app')
當執行 createApp(App).use(store)
的時候,相當于注冊了 store
的插件,會執行到 store
提供的 install
方法,來看看 4.0 版本的 Vuex
是如何實現 install
方法的:
export?class?Store?{install?(app,?injectKey)?{app.provide(injectKey?||?storeKey,?this)app.config.globalProperties.$store?=?this}
}
在注冊插件的時候,內部通過 app.provide
把 store
實例 provide
到了根實例中,此外,store
實例也被添加到了全局屬性的 app.config.globalProperties.$store
中。
這么做之后,我們就可以在組件中輕松訪問到 store
實例了。其中 app.provide
是給 Composition API 方式編寫的組件用的,因為一旦使用了 Composition API ,我們在組件中想訪問 store
的話會在 setup
函數中通過 useStore
API 拿到,如下:
import?{?useStore?}?from?'vuex'
export?default?{setup()?{const?store?=?useStore()}
}
useStore
的實現如下:
import?{?inject?}?from?'vue'
export?const?storeKey?=?'store'
export?function?useStore?(key?=?null)?{return?inject(key?!==?null???key?:?storeKey)
}
原來 Vuex
就是利用了 provide/inject
依賴注入的 API 實現了在組件中訪問到 store
,由于是通過 app.provide
把 store
實例 provide
到根實例中,所以在 app
內部的任意組件中都可以 inject store
實例并訪問了。
除了 Composition API,Vue.js 3.0 依然支持 Options API 的方式去編寫組件,顯然在 Options API 組件中我們依然可以通過 this.$store
訪問到 store
實例,因為實例的查找最終會找到全局 globalProperties
中的屬性。
所以我們看到 provide/inject
在 Vuex
中的作用就是讓組件可以訪問到 store
實例。
Vuex 的其它能力
Vuex
除了管理組件的共享狀態,還有一些其他好用的特性,這里我介紹三個常用的特性。
模塊
由于使用單一狀態樹,應用的所有狀態會集中到一個比較大的對象。當應用變得非常復雜時,store
對象就有可能變得相當臃腫。
為了解決以上問題,Vuex
允許我們將 store
分割成模塊(module
)。每個模塊擁有自己的 state
、getter
、mutation
、action
、甚至是嵌套子模塊——從上至下進行同樣方式的分割:
const?moduleA?=?{state:?()?=>?({?...?}),mutations:?{?...?},actions:?{?...?},getters:?{?...?}
}const?moduleB?=?{state:?()?=>?({?...?}),mutations:?{?...?},actions:?{?...?}
}const?store?=?createStore({modules:?{a:?moduleA,b:?moduleB}
})store.state.a?//?->?moduleA?的狀態
store.state.b?//?->?moduleB?的狀態
另外,在 store
創建之后,你可以使用 store.registerModule
方法動態注冊模塊:
import?{?createStore?}?from?'vuex'const?store?=?createStore({?/*?options?*/?})//?注冊模塊?`myModule`
store.registerModule('myModule',?{//?...
})//?注冊嵌套模塊?`nested/myModule`
store.registerModule(['nested',?'myModule'],?{//?...
})
插件
Vuex
的 store
接受 plugins
選項,這個選項暴露出每次 mutation
的鉤子。Vuex 插件就是一個函數,它接收 store
作為唯一參數:
const?myPlugin?=?(store)?=>?{//?在?store?初始化的時候調用store.subscribe((mutation,?state)?=>?{//?每次提交?mutation?的時候調用})
}
然后像這樣使用:
import?{?createStore?}?from?'vuex'
const?store?=?createStore({//?...plugins:?[myPlugin]
})
官方內置了 Logger
插件用于一般的調試:
import?{?createStore,?createLogger?}?from?'vuex'
const?store?=?createStore({//?...plugins:?[createLogger()]
})
通常我們會在開發環境中使用它,用來輸出提交的 mutation
和生成狀態快照。
嚴格模式
為了保證數據的變化可追蹤,我們要求所有狀態的更改都應該通過提交 mutation
來觸發,因此在嚴格模式下,一旦發生了狀態變更且不是由 mutation
函數引起的,將會拋出錯誤。
我們可以在創建 store
的時候開啟:
const?store?=?createStore({//?...strict:?true
})
由于開啟嚴格模式會有一定的性能損耗,我們也只會在開發環境中開啟它。
總結
綜上,我們發現 Vuex
提供的能力還是很豐富的,而僅僅用 provide/inject
是不能替代 Vuex
的,那么 provide/inject
有哪些應用場景呢?
其實這個在課程中已經說了,我比較推薦在組件庫的開發中使用,因為對于一個特定組件,它和其嵌套的子組件上下文聯系很緊密。
我出這個題主要是希望你能做到以下兩點:
從源碼層面探索,了解
provide/inject
的實現原理。延伸思考
provide/inject
在實現全局數據共享需求與Vuex
的相同與差異。
要記住,分析和思考的過程遠比答案重要。
你好,我是若川,江西人~(點擊藍字了解我)歷時一年只寫了一個學習源碼整體架構系列?有哪些必看的JS庫:jQuery、underscore、lodash、sentry、vuex、axios、koa、redux
關注
若川視野
,回復"pdf" 領取優質前端書籍pdf,回復"1",可加群長期交流學習我的博客地址:https://lxchuan12.gitee.io?歡迎收藏
覺得文章不錯,可以?分享、點贊、在看?呀^_^另外歡迎
留言
交流~
小提醒:若川視野公眾號面試、源碼等文章合集在菜單欄中間
【源碼精選】
按鈕,歡迎點擊閱讀,也可以星標我的公眾號,便于查找