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'
import { createStore } from './store'
import { createRouter } from './router'
import { sync } from 'vuex-router-sync'
import * as filters from './filters'
import * as filters from './util/filters'
// register global utility filters.
Object.keys(filters).forEach(key => {
......
......@@ -27,7 +27,7 @@
</template>
<script>
import { timeAgo } from '../filters'
import { timeAgo } from '../util/filters'
export default {
name: 'news-item',
......
import Vue from 'vue'
import 'es6-promise/auto'
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()
// prime the store with server-initialized state.
......
......@@ -23,12 +23,16 @@ export default context => {
if (!matchedComponents.length) {
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,
// which is resolved when the action is complete and store state has been
// updated.
Promise.all(matchedComponents.map(component => {
return component.preFetch && component.preFetch(store)
return component.fetchData && component.fetchData(
store,
router.currentRoute.params,
context
)
})).then(() => {
isDev && console.log(`data pre-fetch: ${Date.now() - s}ms`)
// 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}`
if (context) {
// server
......
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,
// since they share most of the logic except for the type of items to display.
......@@ -6,10 +9,13 @@ import ItemList from './ItemList.vue'
export default function createListView (type) {
return {
name: `${type}-stories-view`,
// this will be called during SSR to pre-fetch data into the store!
preFetch (store) {
return store.dispatch('FETCH_LIST_DATA', { type })
fetchData (store, params, context) {
return store.dispatch('FETCH_LIST_DATA', { type }).then(() => {
setTitle(camelize(type), context)
})
},
render (h) {
return h(ItemList, { props: { type }})
}
......
......@@ -21,12 +21,9 @@
<script>
import { watchList } from '../api'
import { setTitle } from '../util/set-title'
import Item from '../components/Item.vue'
import Spinner from '../components/Spinner.vue'
const camelize = str => str.charAt(0).toUpperCase() + str.slice(1)
export default {
name: 'item-list',
......@@ -62,12 +59,7 @@ export default {
}
},
serverRendered (context) {
setTitle(camelize(this.type), context)
},
beforeMount () {
setTitle(camelize(this.type))
if (this.$root._isMounted) {
this.loadItems(this.page)
}
......
......@@ -28,55 +28,51 @@
</template>
<script>
import { setTitle } from '../util/set-title'
import { setTitle } from '../util/title'
import Spinner from '../components/Spinner.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 {
name: 'item-view',
components: { Spinner, Comment },
data () {
return {
data: () => ({
loading: true
}
},
}),
computed: {
item () {
return this.$store.state.items[this.$route.params.id]
}
},
// on the server, only fetch the item itself
preFetch: fetchItem,
serverRendered (context) {
setTitle(this.item.title, context)
fetchData (store, params, context) {
return store.dispatch('FETCH_ITEMS', { ids: [params.id] }).then(() => {
const item = store.state.items[params.id]
setTitle(item.title, context)
})
},
// on the client, fetch item + comments
// on the client, fetch all comments
beforeMount () {
fetchItem(this.$store).then(() => {
setTitle(this.item.title)
this.dataPromise.then(() => {
fetchComments(this.$store, this.item).then(() => {
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>
<style lang="stylus">
......
......@@ -17,30 +17,23 @@
</template>
<script>
import { setTitle } from '../util/set-title'
import { setTitle } from '../util/title'
import Spinner from '../components/Spinner.vue'
function fetchUser (store) {
return store.dispatch('FETCH_USER', {
id: store.state.route.params.id
})
}
export default {
name: 'user-view',
components: { Spinner },
computed: {
user () {
return this.$store.state.users[this.$route.params.id]
}
},
preFetch: fetchUser,
serverRendered (context) {
setTitle(this.user.id, context)
},
beforeMount () {
fetchUser(this.$store).then(() => {
setTitle(this.user.id)
fetchData (store, params, context) {
return store.dispatch('FETCH_USER', { id: params.id }).then(() => {
const user = store.state.users[params.id]
setTitle(user.id, context)
})
}
}
......
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