feat: Add Page.watchQuery

This commit is contained in:
Atinux 2017-11-06 18:30:37 +01:00
parent fb44c2eb8e
commit 3d49d8d290
3 changed files with 59 additions and 62 deletions

View File

@ -6,13 +6,14 @@ import {
sanitizeComponent,
resolveRouteComponents,
getMatchedComponents,
getChangedComponentsInstances,
getMatchedComponentsInstances,
flatMapComponents,
setContext,
middlewareSeries,
promisify,
getLocation,
compile
compile,
getQueryDiff
} from './utils'
const noopData = () => { return {} }
@ -20,7 +21,6 @@ const noopFetch = () => {}
// Global shared references
let _lastPaths = []
let _lastComponentsFiles = []
let app
let router
<% if (store) { %>let store<% } %>
@ -99,6 +99,8 @@ function mapTransitions(Components, to, from) {
async function loadAsyncComponents (to, from, next) {
// Check if route path changed (this._pathChanged), only if the page is not an error (for validate())
this._pathChanged = !!app.nuxt.err || from.path !== to.path
this._queryChanged = JSON.stringify(to.query) !== JSON.stringify(from.query)
this._diffQuery = (this._queryChanged ? getQueryDiff(to.query, from.query) : [])
<% if (loading) { %>
if (this._pathChanged && this.$loading.start) {
@ -107,7 +109,24 @@ async function loadAsyncComponents (to, from, next) {
<% } %>
try {
await resolveRouteComponents(to)
const Components = await resolveRouteComponents(to)
<% if (loading) { %>
if (!this._pathChanged && this._queryChanged) {
// Add a marker on each component that it needs to refresh or not
const startLoader = Components.some((Component) => {
const watchQuery = Component.options.watchQuery
if (watchQuery === true) return true
if (Array.isArray(watchQuery)) {
return watchQuery.some((key) => this._diffQuery[key])
}
return false
})
if (startLoader && this.$loading.start) {
this.$loading.start()
}
}
<% } %>
// Call next()
next()
} catch (err) {
err = err || {}
@ -172,7 +191,8 @@ function callMiddleware (Components, context, layout) {
}
async function render (to, from, next) {
if (this._pathChanged === false) return next()
if (this._pathChanged === false && this._queryChanged === false) return next()
console.log('Render')
// nextCalled is true when redirected
let nextCalled = false
@ -259,7 +279,20 @@ async function render (to, from, next) {
await Promise.all(Components.map((Component, i) => {
// Check if only children route changed
Component._path = compile(to.matched[i].path)(to.params)
if (!this._hadError && this._isMounted && Component._path === _lastPaths[i]) {
Component._dataRefresh = false
// Check if Component need to be refreshed (call asyncData & fetch)
// Only if its slug has changed or is watch query changes
if (this._pathChanged && Component._path !== _lastPaths[i]) {
Component._dataRefresh = true
} else if (!this._pathChanged && this._queryChanged) {
const watchQuery = Component.options.watchQuery
if (watchQuery === true) {
Component._dataRefresh = true
} else if (Array.isArray(watchQuery)) {
Component._dataRefresh = watchQuery.some((key) => this._diffQuery[key])
}
}
if (!this._hadError && this._isMounted && !Component._dataRefresh) {
return Promise.resolve()
}
@ -347,26 +380,22 @@ function showNextPage(to) {
// When navigating on a different route but the same component is used, Vue.js
// Will not update the instance data, so we have to update $data ourselves
function fixPrepatch (to, from) {
if (this._pathChanged === false) return
function fixPrepatch(to, ___) {
if (this._pathChanged === false && this._queryChanged === false) return
Vue.nextTick(() => {
const instances = getChangedComponentsInstances(to, from)
const instances = getMatchedComponentsInstances(to)
var dlen = to.matched.length - instances.length
_lastComponentsFiles = instances.map((instance, i) => {
if (!instance) return '';
if (_lastPaths[dlen + i] === instance.constructor._path && typeof instance.constructor.options.data === 'function') {
instances.forEach((instance, i) => {
if (!instance) return
if (instance.constructor._dataRefresh && _lastPaths[i] === instance.constructor._path && typeof instance.constructor.options.data === 'function') {
console.log('Refresh instance', instance)
const newData = instance.constructor.options.data.call(instance)
for (let key in newData) {
Vue.set(instance.$data, key, newData[key])
}
}
return instance.constructor.options.__file
})
showNextPage.call(this, to)
<% if (isDev) { %>
// Hot reloading
@ -523,7 +552,6 @@ async function mountApp(__app) {
if (Components.length) {
_app.setTransitions(mapTransitions(Components, router.currentRoute))
_lastPaths = router.currentRoute.matched.map(route => compile(route.path)(router.currentRoute.params))
_lastComponentsFiles = Components.map(Component => Component.options.__file)
}
// Initialize error handler

View File

@ -70,7 +70,7 @@ export default async ssrContext => {
const renderErrorPage = async () => {
// Load layout for error page
let errLayout = (typeof NuxtError.layout === 'function' ? NuxtError.layout(app.context) : NuxtError.layout)
ssrContext.nuxt.layout = errLayout || ''
ssrContext.nuxt.layout = errLayout || 'default'
await _app.loadLayout(errLayout)
_app.setLayout(errLayout)
await beforeRender()

View File

@ -65,48 +65,6 @@ export function getMatchedComponentsInstances(route) {
}))
}
function getRouteRecordWithParamNames (route) {
return route.matched.map(m => {
var paramNames = m.path.match(new RegExp(':[^\\/\\?]+', 'g'))
if (paramNames !== null) {
paramNames = paramNames.map(function (name) {
return name.substring(1)
})
}
return {
routeRecord: m,
paramNames: paramNames
}
})
}
export function getChangedComponentsInstances (to, from) {
var records = getRouteRecordWithParamNames(to)
var r = []
var parentChange = false
for (var i = 0; i < records.length; i++ ) {
var paramNames = records[i].paramNames
var instances = records[i].routeRecord.instances
instances = Object.keys(instances).map(function (key) {
return instances[key]
})
if (parentChange) {
r = [].concat(r, instances)
} else if (paramNames !== null) {
for (var pi in paramNames) {
var name = paramNames[pi]
if (to.params[name] !== from.params[name]) {
parentChange = true
r = [].concat(r, instances)
break
}
}
}
}
return r
}
export function flatMapComponents(route, fn) {
return Array.prototype.concat.apply([], route.matched.map(function (m, index) {
return Object.keys(m.components).map(function (key) {
@ -116,7 +74,7 @@ export function flatMapComponents(route, fn) {
}
export async function resolveRouteComponents(route) {
await Promise.all(
return await Promise.all(
flatMapComponents(route, async (Component, _, match, key) => {
// If component is a function, resolve it
if (typeof Component === 'function' && !Component.options) {
@ -255,6 +213,17 @@ export function compile(str, options) {
return tokensToFunction(parse(str, options))
}
export function getQueryDiff(toQuery, fromQuery) {
const diff = {}
const queries = { ...toQuery, ...fromQuery }
for (const k in queries) {
if (String(toQuery[k]) !== String(fromQuery[k])) {
diff[k] = true
}
}
return diff
}
/**
* The main path matching regexp utility.
*