2
0
mirror of https://github.com/tenrok/vue-meta.git synced 2026-06-20 16:40:35 +03:00

Merge pull request #18 from declandewet/watcher-logic

circumvent need to call refresh() after async actions
This commit is contained in:
Declan de Wet
2016-11-10 22:31:03 +02:00
committed by GitHub
5 changed files with 54 additions and 42 deletions
+12 -22
View File
@@ -681,38 +681,28 @@ Easy. Instead of defining `metaInfo` as an object, define it as a function and a
## How do I populate `metaInfo` from the result of an asynchronous action? ## How do I populate `metaInfo` from the result of an asynchronous action?
`vue-meta` exposes a method called `refresh` on the client-side that allows you to trigger an update at any given point in time. `vue-meta` will do this for you automatically when your component state changes.
In the same way you access `$meta().inject()` on the server, you can access `$meta().refresh()`. Just make sure that you're using the function form of `metaInfo`:
For example, if you're using Vuex and you have an action that fetches a `post` asynchronously, you should ensure that it returns a promise so that you are notified when the fetching is complete:
```js ```js
{ {
actions: { data () {
async fetchPost ({ commit }, payload) { return {
const post = yield db.fetch('posts', payload.postId) title: 'Foo Bar Baz'
commit('fetchedPost', post) }
}
metaInfo () {
return {
title: this.title
} }
} }
} }
``` ```
Then in your component, you can call `refresh()` to trigger an update once the fetch is complete: Check out the [vuex-async](https://github.com/declandewet/vue-meta/tree/master/examples/vuex-async) example for a far more detailed demonstration if you have doubts.
```js Credit & Thanks for this feature goes to [Sébastien Chopin](https://github.com/Atinux).
{
beforeMount () {
const postId = this.$router.params.id
this.$store.dispatch('fetchPost', { postId })
.then(() => this.$meta().refresh())
}
}
```
Just make sure that whatever data source you're using (`store` if you're using Vuex, component `data` otherwise) has some sane defaults set so Vue doesn't complain about `null` property accessors.
Check out the [vuex-async](https://github.com/declandewet/vue-meta/tree/master/examples/vuex-async) example for a far more detailed demonstration of how this works.
# Examples # Examples
+4 -9
View File
@@ -63,15 +63,10 @@ export default new Vuex.Store({
actions: { actions: {
getPost ({ commit }, payload) { getPost ({ commit }, payload) {
commit('loadingState', { isLoading: true }) commit('loadingState', { isLoading: true })
// we have to return a promise from this action so we know setTimeout(() => {
// when it is finished commit('getPost', payload)
return new Promise((resolve) => { commit('loadingState', { isLoading: false })
setTimeout(() => { }, 2000)
commit('getPost', payload)
resolve()
}, 2000)
})
.then(() => commit('loadingState', { isLoading: false }))
} }
} }
}) })
-4
View File
@@ -18,11 +18,7 @@
name: 'post', name: 'post',
beforeMount () { beforeMount () {
const { slug } = this.$route.params const { slug } = this.$route.params
// since fetching a post is asynchronous,
// we need to call `this.$meta().refresh()`
// to update the meta info
this.$store.dispatch('getPost', { slug }) this.$store.dispatch('getPost', { slug })
.then(() => this.$meta().refresh())
}, },
computed: mapGetters([ computed: mapGetters([
'isLoading', 'isLoading',
+17
View File
@@ -0,0 +1,17 @@
/**
* Performs a batched update. Uses requestAnimationFrame to prevent
* calling a function too many times in quick succession.
* You need to pass it an ID (which can initially be `null`),
* but be sure to overwrite that ID with the return value of batchUpdate.
*
* @param {(null|Number)} id - the ID of this update
* @param {Function} callback - the update to perform
* @return {Number} id - a new ID
*/
export default function batchUpdate (id, callback) {
window.cancelAnimationFrame(id)
return window.requestAnimationFrame(() => {
id = null
callback()
})
}
+21 -7
View File
@@ -1,5 +1,6 @@
import assign from 'object-assign' import assign from 'object-assign'
import $meta from './$meta' import $meta from './$meta'
import batchUpdate from '../client/batchUpdate'
import { import {
VUE_META_KEY_NAME, VUE_META_KEY_NAME,
@@ -33,18 +34,31 @@ export default function VueMeta (Vue, options = {}) {
Vue.prototype.$meta = $meta(options) Vue.prototype.$meta = $meta(options)
// store an id to keep track of DOM updates // store an id to keep track of DOM updates
let requestId = null let batchID = null
// watch for client side component updates // watch for client side component updates
Vue.mixin({ Vue.mixin({
beforeCreate () {
// coerce function-style metaInfo to a computed prop so we can observe
// it on creation
if (typeof this.$options[options.keyName] === 'function') {
this.$options.computed.$metaInfo = this.$options[options.keyName]
}
},
created () {
// if computed $metaInfo exists, watch it for updates & trigger a refresh
// when it changes (i.e. automatically handle async actions that affect metaInfo)
// credit for this suggestion goes to [Sébastien Chopin](https://github.com/Atinux)
if (this.$metaInfo) {
this.$watch('$metaInfo', () => {
// batch potential DOM updates to prevent extraneous re-rendering
batchID = batchUpdate(batchID, () => this.$meta().refresh())
})
}
},
beforeMount () { beforeMount () {
// batch potential DOM updates to prevent extraneous re-rendering // batch potential DOM updates to prevent extraneous re-rendering
window.cancelAnimationFrame(requestId) batchID = batchUpdate(batchID, () => this.$meta().refresh())
requestId = window.requestAnimationFrame(() => {
requestId = null
this.$meta().refresh()
})
} }
}) })
} }