From 3d49d8d2905796f063b171fdd935da721622fe05 Mon Sep 17 00:00:00 2001 From: Atinux Date: Mon, 6 Nov 2017 18:30:37 +0100 Subject: [PATCH] feat: Add Page.watchQuery --- lib/app/client.js | 64 ++++++++++++++++++++++++++++++++++------------- lib/app/server.js | 2 +- lib/app/utils.js | 55 +++++++++------------------------------- 3 files changed, 59 insertions(+), 62 deletions(-) diff --git a/lib/app/client.js b/lib/app/client.js index 0c5a6ff3a7..dc91da015c 100644 --- a/lib/app/client.js +++ b/lib/app/client.js @@ -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 diff --git a/lib/app/server.js b/lib/app/server.js index ac6f004409..df23de17c2 100644 --- a/lib/app/server.js +++ b/lib/app/server.js @@ -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() diff --git a/lib/app/utils.js b/lib/app/utils.js index 11f5f8378b..a7b6603de0 100644 --- a/lib/app/utils.js +++ b/lib/app/utils.js @@ -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. *