Commit ad310a94 authored by Evan You's avatar Evan You

refactor: universal data prefetch function

parent 88be9dd8
...@@ -3,7 +3,7 @@ import App from './App.vue' ...@@ -3,7 +3,7 @@ import App from './App.vue'
import { createStore } from './store' import { createStore } from './store'
import { createRouter } from './router' import { createRouter } from './router'
import { sync } from 'vuex-router-sync' import { sync } from 'vuex-router-sync'
import * as filters from './filters' import * as filters from './util/filters'
// register global utility filters. // register global utility filters.
Object.keys(filters).forEach(key => { Object.keys(filters).forEach(key => {
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
</template> </template>
<script> <script>
import { timeAgo } from '../filters' import { timeAgo } from '../util/filters'
export default { export default {
name: 'news-item', name: 'news-item',
......
import Vue from 'vue'
import 'es6-promise/auto' import 'es6-promise/auto'
import { createApp } from './app' import { createApp } from './app'
// a global mixin to invoke fetchData on the client
Vue.mixin({
beforeMount () {
const { fetchData } = this.$options
if (fetchData) {
this.dataPromise = fetchData(
this.$store,
this.$route.params
)
}
}
})
const { app, router, store } = createApp() const { app, router, store } = createApp()
// prime the store with server-initialized state. // prime the store with server-initialized state.
......
...@@ -23,12 +23,16 @@ export default context => { ...@@ -23,12 +23,16 @@ export default context => {
if (!matchedComponents.length) { if (!matchedComponents.length) {
reject({ code: 404 }) reject({ code: 404 })
} }
// Call preFetch hooks on components matched by the route. // Call fetchData hooks on components matched by the route.
// A preFetch hook dispatches a store action and returns a Promise, // A preFetch hook dispatches a store action and returns a Promise,
// which is resolved when the action is complete and store state has been // which is resolved when the action is complete and store state has been
// updated. // updated.
Promise.all(matchedComponents.map(component => { Promise.all(matchedComponents.map(component => {
return component.preFetch && component.preFetch(store) return component.fetchData && component.fetchData(
store,
router.currentRoute.params,
context
)
})).then(() => { })).then(() => {
isDev && console.log(`data pre-fetch: ${Date.now() - s}ms`) isDev && console.log(`data pre-fetch: ${Date.now() - s}ms`)
// After all preFetch hooks are resolved, our store is now // After all preFetch hooks are resolved, our store is now
......
export function setTitle (title, context) { export const setTitle = (title, context) => {
title = `Vue HN 2.0 | ${title}` title = `Vue HN 2.0 | ${title}`
if (context) { if (context) {
// server // server
......
import ItemList from './ItemList.vue' import ItemList from './ItemList.vue'
import { setTitle } from '../util/title'
const camelize = str => str.charAt(0).toUpperCase() + str.slice(1)
// This is a factory function for dynamically creating root-level list views, // This is a factory function for dynamically creating root-level list views,
// since they share most of the logic except for the type of items to display. // since they share most of the logic except for the type of items to display.
...@@ -6,10 +9,13 @@ import ItemList from './ItemList.vue' ...@@ -6,10 +9,13 @@ import ItemList from './ItemList.vue'
export default function createListView (type) { export default function createListView (type) {
return { return {
name: `${type}-stories-view`, name: `${type}-stories-view`,
// this will be called during SSR to pre-fetch data into the store!
preFetch (store) { fetchData (store, params, context) {
return store.dispatch('FETCH_LIST_DATA', { type }) return store.dispatch('FETCH_LIST_DATA', { type }).then(() => {
setTitle(camelize(type), context)
})
}, },
render (h) { render (h) {
return h(ItemList, { props: { type }}) return h(ItemList, { props: { type }})
} }
......
...@@ -21,12 +21,9 @@ ...@@ -21,12 +21,9 @@
<script> <script>
import { watchList } from '../api' import { watchList } from '../api'
import { setTitle } from '../util/set-title'
import Item from '../components/Item.vue' import Item from '../components/Item.vue'
import Spinner from '../components/Spinner.vue' import Spinner from '../components/Spinner.vue'
const camelize = str => str.charAt(0).toUpperCase() + str.slice(1)
export default { export default {
name: 'item-list', name: 'item-list',
...@@ -62,12 +59,7 @@ export default { ...@@ -62,12 +59,7 @@ export default {
} }
}, },
serverRendered (context) {
setTitle(camelize(this.type), context)
},
beforeMount () { beforeMount () {
setTitle(camelize(this.type))
if (this.$root._isMounted) { if (this.$root._isMounted) {
this.loadItems(this.page) this.loadItems(this.page)
} }
......
...@@ -28,55 +28,51 @@ ...@@ -28,55 +28,51 @@
</template> </template>
<script> <script>
import { setTitle } from '../util/set-title' import { setTitle } from '../util/title'
import Spinner from '../components/Spinner.vue' import Spinner from '../components/Spinner.vue'
import Comment from '../components/Comment.vue' import Comment from '../components/Comment.vue'
function fetchItem (store) {
return store.dispatch('FETCH_ITEMS', {
ids: [store.state.route.params.id]
})
}
// recursively fetch all descendent comments
function fetchComments (store, item) {
if (item.kids) {
return store.dispatch('FETCH_ITEMS', {
ids: item.kids
}).then(() => Promise.all(item.kids.map(id => {
return fetchComments(store, store.state.items[id])
})))
}
}
export default { export default {
name: 'item-view', name: 'item-view',
components: { Spinner, Comment }, components: { Spinner, Comment },
data () {
return { data: () => ({
loading: true loading: true
} }),
},
computed: { computed: {
item () { item () {
return this.$store.state.items[this.$route.params.id] return this.$store.state.items[this.$route.params.id]
} }
}, },
// on the server, only fetch the item itself
preFetch: fetchItem, fetchData (store, params, context) {
serverRendered (context) { return store.dispatch('FETCH_ITEMS', { ids: [params.id] }).then(() => {
setTitle(this.item.title, context) const item = store.state.items[params.id]
setTitle(item.title, context)
})
}, },
// on the client, fetch item + comments
// on the client, fetch all comments
beforeMount () { beforeMount () {
fetchItem(this.$store).then(() => { this.dataPromise.then(() => {
setTitle(this.item.title)
fetchComments(this.$store, this.item).then(() => { fetchComments(this.$store, this.item).then(() => {
this.loading = false this.loading = false
}) })
}) })
} }
} }
// recursively fetch all descendent comments
function fetchComments (store, item) {
if (item.kids) {
return store.dispatch('FETCH_ITEMS', {
ids: item.kids
}).then(() => Promise.all(item.kids.map(id => {
return fetchComments(store, store.state.items[id])
})))
}
}
</script> </script>
<style lang="stylus"> <style lang="stylus">
......
...@@ -17,30 +17,23 @@ ...@@ -17,30 +17,23 @@
</template> </template>
<script> <script>
import { setTitle } from '../util/set-title' import { setTitle } from '../util/title'
import Spinner from '../components/Spinner.vue' import Spinner from '../components/Spinner.vue'
function fetchUser (store) {
return store.dispatch('FETCH_USER', {
id: store.state.route.params.id
})
}
export default { export default {
name: 'user-view', name: 'user-view',
components: { Spinner }, components: { Spinner },
computed: { computed: {
user () { user () {
return this.$store.state.users[this.$route.params.id] return this.$store.state.users[this.$route.params.id]
} }
}, },
preFetch: fetchUser,
serverRendered (context) { fetchData (store, params, context) {
setTitle(this.user.id, context) return store.dispatch('FETCH_USER', { id: params.id }).then(() => {
}, const user = store.state.users[params.id]
beforeMount () { setTitle(user.id, context)
fetchUser(this.$store).then(() => {
setTitle(this.user.id)
}) })
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment