diff --git a/examples/keepalive-view/app.js b/examples/keepalive-view/app.js index 2155782b8a..e68395f3fe 100644 --- a/examples/keepalive-view/app.js +++ b/examples/keepalive-view/app.js @@ -27,6 +27,48 @@ const IndexChild2 = { template: '
index child2
' } const Home = { template: '
home
' } +const Parent = { template: '' } + +const RequiredProps = { + template: '
props from route config is: {{msg}}
', + props: { + msg: { + type: String, + required: true + } + } +} + +const savedSilent = Vue.config.silent +const savedWarnHandler = Vue.config.warnHandler + +const CatchWarn = { + template: `
{{catchedWarn ? 'catched missing prop warn' : 'no missing prop warn'}}
`, + data () { + return { + catchedWarn: false + } + }, + beforeRouteEnter (to, from, next) { + let missPropWarn = false + Vue.config.silent = false + Vue.config.warnHandler = function (msg, vm, trace) { + if (/Missing required prop/i.test(msg)) { + missPropWarn = true + } + } + next(vm => { + vm.catchedWarn = missPropWarn + }) + }, + beforeRouteLeave (to, from, next) { + // restore vue config + Vue.config.silent = savedSilent + Vue.config.warnHandler = savedWarnHandler + next() + } +} + const router = new VueRouter({ mode: 'history', base: __dirname, @@ -58,6 +100,23 @@ const router = new VueRouter({ path: '/with-guard2', name: 'with-guard2', component: WithGuard + }, + { + path: '/config-required-props', + component: Parent, + children: [ + { + path: 'child', + component: RequiredProps, + props: { + msg: 'ok' + } + } + ] + }, + { + path: '/catch-warn', + component: CatchWarn } ] }) @@ -72,6 +131,8 @@ new Vue({
  • /home
  • /with-guard1
  • /with-guard2
  • +
  • /config-required-props/child
  • +
  • /catch-warn
  • diff --git a/src/components/view.js b/src/components/view.js index 3f440d5b4b..b96b29efd4 100644 --- a/src/components/view.js +++ b/src/components/view.js @@ -41,17 +41,32 @@ export default { // render previous view if the tree is inactive and kept-alive if (inactive) { - return h(cache[name], data, children) + const cachedData = cache[name] + const cachedComponent = cachedData && cachedData.component + if (cachedComponent) { + // #2301 + // pass props + if (cachedData.configProps) { + passProps(cachedComponent, data, cachedData.route, cachedData.configProps) + } + return h(cachedComponent, data, children) + } else { + // render previous empty view + return h() + } } const matched = route.matched[depth] - // render empty node if no matched route - if (!matched) { + const component = matched && matched.components[name] + + // render empty node if no matched route or no config component + if (!matched || !component) { cache[name] = null return h() } - const component = cache[name] = matched.components[name] + // cache component + cache[name] = { component } // attach instance registration hook // this will be called in the instance's injected lifecycle hooks @@ -83,25 +98,38 @@ export default { } } - // resolve props - let propsToPass = data.props = resolveProps(route, matched.props && matched.props[name]) - if (propsToPass) { - // clone to prevent mutation - propsToPass = data.props = extend({}, propsToPass) - // pass non-declared props as attrs - const attrs = data.attrs = data.attrs || {} - for (const key in propsToPass) { - if (!component.props || !(key in component.props)) { - attrs[key] = propsToPass[key] - delete propsToPass[key] - } - } + const configProps = matched.props && matched.props[name] + // pass props + if (configProps) { + // add route and config props to cache data + extend(cache[name], { + route, + configProps + }) + passProps(component, data, route, configProps) } return h(component, data, children) } } +function passProps (component, data, route, configProps) { + // resolve props + let propsToPass = data.props = resolveProps(route, configProps) + if (propsToPass) { + // clone to prevent mutation + propsToPass = data.props = extend({}, propsToPass) + // pass non-declared props as attrs + const attrs = data.attrs = data.attrs || {} + for (const key in propsToPass) { + if (!component.props || !(key in component.props)) { + attrs[key] = propsToPass[key] + delete propsToPass[key] + } + } + } +} + function resolveProps (route, config) { switch (typeof config) { case 'undefined': diff --git a/test/e2e/specs/keepalive-view.js b/test/e2e/specs/keepalive-view.js index 5e36712382..eedafc09e6 100644 --- a/test/e2e/specs/keepalive-view.js +++ b/test/e2e/specs/keepalive-view.js @@ -9,7 +9,7 @@ module.exports = { browser .url('http://localhost:8080/keepalive-view/') .waitForElementVisible('#app', 1000) - .assert.count('li a', 5) + .assert.count('li a', 7) .click('li:nth-child(1) a') .assert.containsText('.view', 'index child1') @@ -35,6 +35,15 @@ module.exports = { .click('li:nth-child(4) a') .assert.containsText('.view', 'with-guard1: 3') + // missing props in nested routes with keep alive + // https://github.com/vuejs/vue-router/issues/2301 + .click('li:nth-child(6) a') + .assert.containsText('.view', 'props from route config is: ok') + .click('li:nth-child(7) a') + .assert.containsText('.view', 'no missing prop warn') + .click('li:nth-child(6) a') + .assert.containsText('.view', 'props from route config is: ok') + .end() } }