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, sanitizeComponent,
resolveRouteComponents, resolveRouteComponents,
getMatchedComponents, getMatchedComponents,
getChangedComponentsInstances, getMatchedComponentsInstances,
flatMapComponents, flatMapComponents,
setContext, setContext,
middlewareSeries, middlewareSeries,
promisify, promisify,
getLocation, getLocation,
compile compile,
getQueryDiff
} from './utils' } from './utils'
const noopData = () => { return {} } const noopData = () => { return {} }
@ -20,7 +21,6 @@ const noopFetch = () => {}
// Global shared references // Global shared references
let _lastPaths = [] let _lastPaths = []
let _lastComponentsFiles = []
let app let app
let router let router
<% if (store) { %>let store<% } %> <% if (store) { %>let store<% } %>
@ -99,6 +99,8 @@ function mapTransitions(Components, to, from) {
async function loadAsyncComponents (to, from, next) { async function loadAsyncComponents (to, from, next) {
// Check if route path changed (this._pathChanged), only if the page is not an error (for validate()) // 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._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 (loading) { %>
if (this._pathChanged && this.$loading.start) { if (this._pathChanged && this.$loading.start) {
@ -107,7 +109,24 @@ async function loadAsyncComponents (to, from, next) {
<% } %> <% } %>
try { 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() next()
} catch (err) { } catch (err) {
err = err || {} err = err || {}
@ -172,7 +191,8 @@ function callMiddleware (Components, context, layout) {
} }
async function render (to, from, next) { 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 // nextCalled is true when redirected
let nextCalled = false let nextCalled = false
@ -259,7 +279,20 @@ async function render (to, from, next) {
await Promise.all(Components.map((Component, i) => { await Promise.all(Components.map((Component, i) => {
// Check if only children route changed // Check if only children route changed
Component._path = compile(to.matched[i].path)(to.params) 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() return Promise.resolve()
} }
@ -347,26 +380,22 @@ function showNextPage(to) {
// When navigating on a different route but the same component is used, Vue.js // 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 // Will not update the instance data, so we have to update $data ourselves
function fixPrepatch (to, from) { function fixPrepatch(to, ___) {
if (this._pathChanged === false) return if (this._pathChanged === false && this._queryChanged === false) return
Vue.nextTick(() => { Vue.nextTick(() => {
const instances = getChangedComponentsInstances(to, from) const instances = getMatchedComponentsInstances(to)
var dlen = to.matched.length - instances.length instances.forEach((instance, i) => {
_lastComponentsFiles = instances.map((instance, i) => { if (!instance) return
if (!instance) return ''; if (instance.constructor._dataRefresh && _lastPaths[i] === instance.constructor._path && typeof instance.constructor.options.data === 'function') {
console.log('Refresh instance', instance)
if (_lastPaths[dlen + i] === instance.constructor._path && typeof instance.constructor.options.data === 'function') {
const newData = instance.constructor.options.data.call(instance) const newData = instance.constructor.options.data.call(instance)
for (let key in newData) { for (let key in newData) {
Vue.set(instance.$data, key, newData[key]) Vue.set(instance.$data, key, newData[key])
} }
} }
return instance.constructor.options.__file
}) })
showNextPage.call(this, to) showNextPage.call(this, to)
<% if (isDev) { %> <% if (isDev) { %>
// Hot reloading // Hot reloading
@ -523,7 +552,6 @@ async function mountApp(__app) {
if (Components.length) { if (Components.length) {
_app.setTransitions(mapTransitions(Components, router.currentRoute)) _app.setTransitions(mapTransitions(Components, router.currentRoute))
_lastPaths = router.currentRoute.matched.map(route => compile(route.path)(router.currentRoute.params)) _lastPaths = router.currentRoute.matched.map(route => compile(route.path)(router.currentRoute.params))
_lastComponentsFiles = Components.map(Component => Component.options.__file)
} }
// Initialize error handler // Initialize error handler

View File

@ -70,7 +70,7 @@ export default async ssrContext => {
const renderErrorPage = async () => { const renderErrorPage = async () => {
// Load layout for error page // Load layout for error page
let errLayout = (typeof NuxtError.layout === 'function' ? NuxtError.layout(app.context) : NuxtError.layout) 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) await _app.loadLayout(errLayout)
_app.setLayout(errLayout) _app.setLayout(errLayout)
await beforeRender() 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) { export function flatMapComponents(route, fn) {
return Array.prototype.concat.apply([], route.matched.map(function (m, index) { return Array.prototype.concat.apply([], route.matched.map(function (m, index) {
return Object.keys(m.components).map(function (key) { return Object.keys(m.components).map(function (key) {
@ -116,7 +74,7 @@ export function flatMapComponents(route, fn) {
} }
export async function resolveRouteComponents(route) { export async function resolveRouteComponents(route) {
await Promise.all( return await Promise.all(
flatMapComponents(route, async (Component, _, match, key) => { flatMapComponents(route, async (Component, _, match, key) => {
// If component is a function, resolve it // If component is a function, resolve it
if (typeof Component === 'function' && !Component.options) { if (typeof Component === 'function' && !Component.options) {
@ -255,6 +213,17 @@ export function compile(str, options) {
return tokensToFunction(parse(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. * The main path matching regexp utility.
* *