1. Vuex實戰
上次文章介紹了Vue元件化之間通訊的各種姿勢,其中vuex基本算是終極解決方案了,這個沒啥說的,直接貼程式碼把
所謂各大框架的資料管理框架,原則上來說,就是獨立團大了,所有事都團長處理太累了,所以老李只管軍事,槍彈菸酒面這些資料,交給趙政委,趙政委就是我們們的Vuex,從此以後 全團共享的資料,都必須得經過趙政委統一進行調配
我的風格就是用過的東西,都喜歡造個輪子,實戰使用 只是基礎而已,話不多說看程式碼
// store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state,n=1) {
state.count += n
}
},
actions:{
incrementAsync({commit}){
setTimeout(()=>{
commit('increment',2)
},1000)
}
}
})
複製程式碼
// main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false
new Vue({
store,
render: h => h(App),
}).$mount('#app')
複製程式碼
<template>
<div id="app">
<div>衝啊,手榴彈扔了{{$store.state.count}}個</div>
<button @click="add">扔一個</button>
<button @click="addAsync">蓄力扔倆</button>
</div>
</template>
<script>
export default {
name: 'app',
methods:{
add(){
this.$store.commit('increment')
},
addAsync(){
this.$store.dispatch('incrementAsync')
}
}
}
</script>
複製程式碼
2. 實現自己Vuex
要實現Vue,首先要實現的 就是Vue.use(Vuex),這是vue安裝外掛的機制,需要Vuex對外暴露一個install方法,會把Vue傳遞給install這個函式,我們們來小小的測試一下下
3. 實現外掛機制
新建zhao(趙政委).js 對外暴露install 方法,內部在Vue的元件上掛載一個$store變數
class Store {
constructor() {
this.name = '趙政委'
}
}
function install(Vue) {
Vue.prototype.$store = new Store()
}
export default { Store, install }
複製程式碼
// app.vue
<template>
<div id="app">
</div>
</template>
<script>
export default {
name: 'app',
created(){
console.log(this.$store)
}
}
</script>
// output Store {name: "趙政委"}
複製程式碼
4. 傳遞store
真正使用的時候,store是通過new Vue傳遞進來的,我們需要使用mixin在beforeCreated來掛載,這樣才能通過this.$option獲取傳遞進來的store
// zhao.js
class Store {
constructor() {
this.name = '趙政委'
}
}
function install(Vue) {
Vue.mixin({
beforeCreate(){
// 這樣才能獲取到傳遞進來的store
// 只有root元素才有store,所以判斷一下
if(this.$options.store){
Vue.prototype.$store = store
}
}
})
// console.log(this)
}
export default { Store, install }
複製程式碼
// store.js
import Vue from 'vue'
import Vuex from './zhao'
Vue.use(Vuex)
export default new Vuex.Store()
複製程式碼
5. state
單純的資料渲染比較easy
// zhao.js
class Store {
constructor(options={}) {
// this.name = '趙政委'
this.state = options.state
}
}
複製程式碼
6. mutation
修改資料,並且需要通知到元件,這個需要資料是響應式的,我們需要Vue的響應式支援,所以這裡也可以看到Vuex是和Vue強繫結的,不能脫離vue單獨使用
由於install的時候會傳遞一個Vue,我們維護一個全域性變數,就不用再import vue了,如果zhao.js單獨釋出,減小包體積
mutation實現也比較簡單,記錄一下mutation的函式,commit的時候更新資料即可
// zhao.js
let Vue
class Store {
constructor(options={}) {
// this.name = '趙政委'
this.state = options.state || {}
this.mutations = options.mutations || {}
}
commit(type,arg){
if(!this.mutations[type]){
console.log('不合法的mutation')
return
}
this.mutations[type](this.state,arg)
}
}
function install(_Vue) {
// 這樣store執行的時候,就有了Vue,不用import
// 這也是為啥 Vue.use必須在新建store之前
Vue = _Vue
_Vue.mixin({
beforeCreate(){
// 這樣才能獲取到傳遞進來的store
// 只有root元素才有store,所以判斷一下
if(this.$options.store){
_Vue.prototype.$store = this.$options.store
}
}
})
}
export default { Store, install }
複製程式碼
// store.js
import Vue from 'vue'
import Vuex from './zhao'
Vue.use(Vuex)
export default new Vuex.Store({
state:{
count:0
},
mutations:{
increment (state,n=1) {
state.count += n
}
}
})
複製程式碼
每次點選 count都變了,頁面並沒有相應
7. 響應式state
想響應式通知到頁面,最方面的莫過於使用Vue的響應式機制,讓state程式設計相應式
this.state = new Vue({
data:options.state
})
複製程式碼
8. action
非同步actions,mutation 必須同步執行這個限制麼?Action 就不受約束!由於有非同步任務,commit單獨執行,所以需要用箭頭函式,確保內部的this指向
let Vue
class Store {
constructor(options={}) {
// this.name = '趙政委'
this.state = new Vue({
data:options.state
})
this.mutations = options.mutations || {}
this.actions = options.actions
}
commit = (type,arg)=>{
this.mutations[type](this.state,arg)
}
dispatch(type, arg){
this.actions[type]({
commit:this.commit,
state:this.state
}, arg)
}
}
複製程式碼
bingo
一個賊拉迷你的vuex基本完成,還有幾個概念,需要繼續完善
9. getter
類似computed,實現也不難 ,使用Object.defineProperty代理一個getter即可,獲取getter內部的值,直接執行函式計算。掛載在store.getters之上
handleGetters(getters){
this.getters = {}
Object.keys(getters).forEach(key=>{
Object.defineProperty(this.getters,key,{
get:()=>{
return getters[key](this.state)
}
})
})
}
複製程式碼
//store.js
state:{
count:0
},
getters:{
killCount(state){
return state.count * 2
}
},
複製程式碼
<div>炸死了{{$store.getters.killCount}}個櫃子</div>
複製程式碼
10. modules
vuex支援拆包,每個module有自己的state,getter,mutations,和actions,所以需要專門引入喝安裝modules,並且遞迴支援深層巢狀,之前的handleGetters之類的東東,每個module都得執行一下
深層次巢狀,state需要getter代理一下
11. 註冊modules
掛載到root上
register(path, module){
const newModule = {
children: {},
module: module,
state: module.state
}
if(path.length){
// path有值,子模組
const parent = path.slice(0, -1).reduce((module, key) => {
return module.children(key);
}, this.root);
parent.children[path[path.length - 1]] = newModule;
}else{
// 空 就是根目錄
this.root = newModule
}
if(module.modules){
this.forEachObj(module.modules,(name,mod)=>{
// console.log(123,name,mod)
this.register([...path,name],mod)
})
}
}
複製程式碼
12. 啟動modules
installModules(state,path,module){
// 安裝所有的module的mutation,actions,
if(path.length>0){
const moduleName = this.last(path);
// 預設名字都註冊在一個名稱空間裡
Vue.set(state, moduleName,module.state)
}
this.forEachObj(module.children, (key,child)=>{
this.installModules(state, [...path,key],child)
})
}
複製程式碼
constructor(options={}) {
// this.name = '趙政委'
this._vm = new Vue({
data:{
state:options.state
}
})
// 根模組
this.root = null
this.mutations = options.mutations || {}
this.actions = options.actions
this.handleGetters(options.getters)
// 註冊一下module,遞迴,變成一個大的物件 掛載到root
this.register([], options)
this.installModules(options.state, [], this.root)
// this.installModules(options.modules)
}
get state(){
return this._vm._data.state
}
複製程式碼
installModules(state,path,module){
// 安裝所有的module的mutation,actions,
if(path.length>0){
const moduleName = this.last(path);
// 預設名字都註冊在一個名稱空間裡
Vue.set(state, moduleName,module.state)
}
// 設定上下文,獲取state要遍歷 path
const context = {
dispatch: this.dispatch,
commit: this.commit,
}
Object.defineProperties(context, {
getters: {
get: () => this.getters
},
state: {
get: () => {
let state = this.state;
return path.length ? path.reduce((state, key) => state[key], state) : state
}
}
})
// 註冊mutations 傳遞正確的state
this.registerMutations(module.module.mutations,context)
// 註冊action
this.registerActions(module.module.actions,context)
// 註冊getters
this.registerGetters(module.module.getters,context)
// 遞迴
this.forEachObj(module.children, (key,child)=>{
this.installModules(state, [...path,key],child)
})
}
複製程式碼
13. store.js
// zhao.js
let Vue
class Store {
constructor(options={}) {
// this.name = '趙政委'
this._vm = new Vue({
data:{
state:options.state
}
})
// 根模組
this.root = null
this.mutations = {}
this.actions = {}
this.getters = {}
// 註冊一下module,遞迴,變成一個大的物件 掛載到root
this.register([], options)
this.installModules(options.state, [], this.root)
}
get state(){
return this._vm._data.state
}
register(path, module){
const newModule = {
children: {},
module: module,
state: module.state
}
if(path.length){
// path有值,子模組
const parent = path.slice(0, -1).reduce((module, key) => {
return module.children(key);
}, this.root);
parent.children[path[path.length - 1]] = newModule;
}else{
// 空 就是根目錄
this.root = newModule
}
if(module.modules){
this.forEachObj(module.modules,(name,mod)=>{
// console.log(123,name,mod)
this.register([...path,name],mod)
})
}
}
forEachObj(obj={},fn){
Object.keys(obj).forEach(key=>{
fn(key, obj[key])
})
}
commit = (type,arg)=>{
this.mutations[type](this.state,arg)
}
dispatch(type, arg){
this.actions[type]({
commit:this.commit,
state:this.state
}, arg)
}
last(arr){
return arr[arr.length-1]
}
installModules(state,path,module){
// 安裝所有的module的mutation,actions,
if(path.length>0){
const moduleName = this.last(path);
// 預設名字都註冊在一個名稱空間裡
Vue.set(state, moduleName,module.state)
}
// 設定上下文,獲取state要遍歷 path
const context = {
dispatch: this.dispatch,
commit: this.commit,
}
Object.defineProperties(context, {
getters: {
get: () => this.getters
},
state: {
get: () => {
let state = this.state;
return path.length ? path.reduce((state, key) => state[key], state) : state
}
}
})
// 註冊mutations 傳遞正確的state
this.registerMutations(module.module.mutations,context)
// 註冊action
this.registerActions(module.module.actions,context)
// 註冊getters
this.registerGetters(module.module.getters,context)
// 遞迴
this.forEachObj(module.children, (key,child)=>{
this.installModules(state, [...path,key],child)
})
}
handleGetters(getters){
Object.keys(getters).forEach(key=>{
Object.defineProperty(this.getters,key,{
get:()=>{
return getters[key](this.state)
}
})
})
}
registerGetters(getters, context){
this.forEachObj(getters,(key,getter)=>{
Object.defineProperty(this.getters,key,{
get:()=>{
return getter(
// module的state
context.state,
context.getters,
// 最外層的store
this.state
)
}
})
})
}
registerMutations(mutations, context){
if(mutations){
this.forEachObj(mutations, (key,mutation)=>{
this.mutations[key] = ()=>{
mutation.call(this, context.state)
}
})
}
}
registerActions(actions,context){
if(actions){
this.forEachObj(actions, (key,action)=>{
this.actions[key] = ()=>{
action.call(this, context)
}
})
}
}
}
function install(_Vue) {
// 這樣store執行的時候,就有了Vue,不用import
// 這也是為啥 Vue.use必須在新建store之前
Vue = _Vue
_Vue.mixin({
beforeCreate(){
// 這樣才能獲取到傳遞進來的store
// 只有root元素才有store,所以判斷一下
if(this.$options.store){
_Vue.prototype.$store = this.$options.store
}
}
})
}
export default { Store, install }
複製程式碼
14. store.js
// store.js
import Vue from 'vue'
import Vuex from './zhao'
Vue.use(Vuex)
const commander = {
state: {
num: 17
},
mutations: {
fire(state) {
state.num -= 1
}
},
getters:{
fireCount(state){
return (17-state.num) *100
},
totalCount(state,getters,rootState){
return getters.fireCount + rootState.count*2
}
},
actions: {
fireAsync({commit}) {
setTimeout(()=>{
commit('fire');
},2000)
}
}
}
export default new Vuex.Store({
modules:{
commander
},
state:{
count:0
},
getters:{
killCount(state){
return state.count * 2
}
},
mutations:{
increment (state,n=1) {
state.count += n
}
},
actions:{
incrementAsync(context){
console.log(context,123)
setTimeout(()=>{
context.commit('increment',2)
},1000)
}
}
})
複製程式碼
15. 元件使用
<template>
<div id="app">
<p>衝啊,手榴彈扔了{{$store.state.count}}個</p>
<p>炸死了{{$store.getters.killCount}}個櫃子</p>
<p>
<button @click="add">扔一個</button>
<button @click="addAsync">蓄力扔倆</button>
</p>
<Battalion></Battalion>
</div>
</template>
<script>
import Battalion from '@/components/Battalion'
export default {
name: 'app',
components:{
Battalion
},
created(){
},
methods:{
add(){
this.$store.commit('increment')
console.log(this.$store.state)
console.log(this.$store.state.getters)
},
addAsync(){
this.$store.dispatch('incrementAsync')
}
}
}
</script>
<style>
div{
border:1px solid red;
margin:20px;
padding:20px;
}
</style>
複製程式碼
子元件
<template>
<div>
<p> 義大利炮還有{{$store.state.commander.num}}發炮彈 </p>
<p> 義大利炮炸死了{{$store.getters.fireCount}}個鬼子 和手榴彈一起是{{$store.getters.totalCount}} </p>
<button @click="fire">開炮</button>
<button @click="fireAsync">一會再開炮</button>
</div>
</template>
<script>
export default {
name: 'pageA',
mounted(){
},
methods:{
fire(){
this.$store.commit('fire')
},
fireAsync(){
this.$store.dispatch('fireAsync')
}
}
}
</script>
複製程式碼
16. mapState
其實很簡單,直接返回對應的值就可以了,computed內部可以通過this.$store拿到,程式碼就呼之欲出了
function mapState(obj){
const ret = {}
Object.keys(obj).forEach((key)=>{
// 支援函式
let val = obj[key]
ret[key] = function(){
const state = this.$store.state
return typeof val === 'function'
? val.call(this, state)
: state[val]
}
})
return ret
}
複製程式碼
17. mapMutations
function mapMutations(mutations){
const ret = {}
mutations.forEach((key)=>{
ret[key] = function(){
const commit = this.$store.commit
commit(key)
}
})
return ret
}
複製程式碼
mapGetters和mapActions原理類似 不贅述了 白白