身為一個專業農夫,持續耕耘一些地雷也是一件天經地義的事。不過這也不算是地雷,只是因為需求問題,所以需要一些比較奇技淫巧的處理方法。
不過也沒有很奇技淫巧啦其實。
Vuex Plugins
首先,這裡需要對於 Vuex 有一定的認識,比較基本的就不贅述,請去看文件,有簡體中文可以勉強看看,不然你看英文的也可以。
我們要提的是 Plugins 這一塊,
前情提要
我們用 Vuex 當然是希望我們在整個應用程式內,資料盡量都是統一的,但是,如果遇上了非同步傳輸的資料,例如 AJAX 跟後面拿東西(更甚是 WebSocket 拿回來的),你在你的元件內就不一定會照你的方式呈現。
以下都以 Vue 2.0 的作法,請留意。
我們先來一個 A 元件,
<template>
<section>
<header><h1>Hello World</h1></header>
<body-component></body-component>
<aside>
<section>
<h2>{{ getUserData.name }}</h2>
</section>
</aside>
</section>
</template>
<script>
import { mapGetter } from 'vuex'
import bodyComponent from './bodyComponent.vue'
export default {
components: {
'body-component': bodyComponent
},
computed: mapGetter([
'getUserData'
]),
data () {
return {}
},
created () {
}
}
</script>
以上會遇到一個問題,
TypeError: Cannot read property 'name' of undefined
然後,想說好,那我先把東西設定好,總可以了吧?
<script>
import { mapGetters, mapActions } from 'vuex'
import bodyComponent from './bodyComponent.vue'
export default {
components: {
'body-component': bodyComponent
},
computed: mapGetters([
'getUserData'
]),
methods: mapActions([
'fetchUserData'
]),
data () {
this.fetchUserData()
return {}
},
created () {
}
}
</script>
以上會遇到一個問題,
TypeError: Cannot read property 'name' of undefined
原因是,fetchUserData()
如果是 AJAX 的話,由於樣版會先渲染出來,所以一樣會告訴你上述的錯誤。那,再設定一次總可以了吧?
<template>
<section>
<header><h1>Hello World</h1></header>
<body-component></body-component>
<aside>
<section>
<h2>{{ userData.name }}</h2>
</section>
</aside>
</section>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import bodyComponent from './bodyComponent.vue'
export default {
components: {
'body-component': bodyComponent
},
computed: mapGetters([
'getUserData'
]),
methods: mapActions([
'fetchUserData'
]),
data () {
return {
userData: {
name: ''
}
}
},
created () {
this.fetchUserData().then(() => {
this.userData.name = this.getUserData.name
})
}
}
</script>
這樣每次都要 fetchUserData()
不就很麻煩?那改用 watch
好了,
<script>
import { mapGetters, mapActions } from 'vuex'
import bodyComponent from './bodyComponent.vue'
export default {
components: {
'body-component': bodyComponent
},
computed: mapGetters([
'getUserData'
]),
methods: mapActions([
'fetchUserData'
]),
data () {
return {
userData: {
name: ''
}
}
},
created () {
this.fetchUserData()
},
watch: {
getUserData (data) {
this.userData.name = data.name
}
}
}
</script>
好啦,那這樣 A 元件好像就沒什麼問題了。接著我們來看 bodyComponent
可能會出什麼狀況,
<template>
<article>
<h2>{{ title }}</h2>
<h4>{{ getUserData.name }}</h4>
<p v-html="content"></p>
</article>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: mapGetters([
'getUserData'
]),
data () {
return {
title: '我是標題',
content: '我是內容'
}
}
}
</script>
這個還是會有這個問題,
TypeError: Cannot read property 'name' of undefined
可是我在 A 元件拿過了啊?
但是因為他是非同步傳輸所以不知道什麼時候會拿回來給你。
好吧那我只好再 watch
一次,
<template>
<article>
<h2>{{ title }}</h2>
<h4>{{ userData.name }}</h4>
<p v-html="content"></p>
</article>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: mapGetters([
'getUserData'
]),
data () {
return {
title: '我是標題',
content: '我是內容',
userData: {
name: ''
}
}
},
watch: {
getUserData (data) {
this.userData.name = data.name
}
}
}
</script>
這樣好像就沒什麼狀況。但是,如果每一個元件都這樣搞,這樣不就搞死人了。而且 Vuex 既然是放資料的,既然他資料都統一存放,我要隨時提取,應該就是拿到我要的資料才對,除非你沒給。
所以,這個時候就應該是 Plugins 派上用場的時機(在 Vuex 1.0 的時候,他叫做 middleware
,是後來 2.0 才改名叫做 plugins
,用法類似。
操作方式與元件生命週期
元件生命週期請先看一下官方解說,
我先不提 Plugins 怎麼操作,我們看一下元件常用的地方,
export default {
computed: mapGetters(['getUserData']),
data () {
console.log(this.getUserData.name)
return {}
},
beforeCreate () {
console.log(this.getUserData.name)
},
created () {
console.log(this.getUserData.name)
},
mounted () {
console.log(this.getUserData.name)
}
}
以上會印出,
TypeError: Cannot read property 'name' of undefined
TypeError: Cannot read property 'name' of undefined
"hinablue"
"hinablue"
好,現在你知道 Vuex 在哪些地方不會有效果了,接著來看 Plugins 怎麼寫。首先,我們需要寫一個簡易的 Plugin 來處理拿資料的部分,非同步處理的部分。
export default function fetchUserData () {
return store => {
store.subscribe((mutation, state) => {
if (mutation.type === 'router/ROUTE_CHANGED') {
// 由於 Vue-Router 會觸發 ROUTE_CHANGED
// 所以我們只在這個時候作一次,避免重複被觸發
store.dispatch('fetchUserData')
}
})
}
}
就這樣,把他加入你的 Vuex.store
import fetchUserData from './plugins/fetchUserData'
const store = new Vuex.Store({
modules: {
...
},
strict: process.env.NODE_ENV !== 'production',
plugins: [fetchUserData()]
})
然後,我們回到 A 元件,就可以回到最初,不需要 watch
也不用額外去抓資料回來,
<template>
<section>
<header><h1>Hello World</h1></header>
<body-component></body-component>
<aside>
<section>
<h2>{{ getUserData.name }}</h2>
</section>
</aside>
</section>
</template>
<script>
import { mapGetter } from 'vuex'
import bodyComponent from './bodyComponent.vue'
export default {
components: {
'body-component': bodyComponent
},
computed: mapGetter([
'getUserData'
]),
data () {
return {}
},
created () {
}
}
</script>
然後 bodyComponent
就可以改回這樣,
<template>
<article>
<h2>{{ title }}</h2>
<h4>{{ getUserData.name }}</h4>
<p v-html="content"></p>
</article>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: mapGetters([
'getUserData'
]),
data () {
return {
title: '我是標題',
content: '我是內容'
}
}
}
</script>
然後,還有一種例外,就是當你的 Plugin 拿不到資料的時候,一樣會出現這個錯誤,
TypeError: Cannot read property 'name' of undefined
至於這個問題要怎麼解?
- 只能在 Plugin 中先給 預設值
- 在取資料失敗的時候給 預設值
dispatch
或是commit
拿到失敗的資料給 預設值- 不管怎樣你就是要給 預設值
Plugin 的執行順序會比元件要早,但是並不是 最早的,剛剛上述的元件生命週期,我列出來給你們看了。雖然我在 Plugin 中先賦予值(即便我不是非同步拿的資料),在元件當中,還是得在 created()
之後才拿得到。
預設值在這裡是必須的,不然,在開發模式都會強制被中斷(正式機會不會壞?你可以試試看 XD
所以,倘若你是在 data()
當中去處理,就會拿不到東西。但是,如果你有使用 Vue-Router 的話,在 beforeRouteEnter
是拿得到的,因為他是在 router/ROUTE_CHANGED
之後才觸發。
所以,這樣是可以的,
beforeRouterEnter (to, from, next) {
next(vm => {
console.log(vm.getUserData.name)
})
},
data () {
console.log(this.getUserData.name)
return {}
}
這樣是可以拿到東西的,反之 data()
是無法拿到的,
"hinablue"
TypeError: Cannot read property 'name' of undefined
例外
如果是在 App 的最上層,以上的作法會出現例外。
由於 Vuex 在最上層並不會進入 router/ROUTE_CHANGED
這個狀態,所以上述的東西就不會被執行。這個時候必須要把某些東西,移出 store.subscribe
到外層去,但是這樣會沒有正確的 mutation
, state
可以用(特別是你有跟 Router 同步的時候,這個時候會拿不到 router
的數值)。
小結
API 文件寫得好,預設值沒煩惱。
但是我好像寫得不是很好(已哭