diff --git a/examples/keepalive-view/app.js b/examples/keepalive-view/app.js index 37563aa32..341fed97e 100644 --- a/examples/keepalive-view/app.js +++ b/examples/keepalive-view/app.js @@ -27,7 +27,52 @@ const IndexChild2 = { template: '
index child2
' } const Home = { template: '
home
' } -const ViewWithKeepalive = { template: '' } +const ViewWithKeepalive = { + template: '' +} + +const Parent = { template: '
msg: {{ msg }}
', props: ['msg'] } + +const RequiredProps = { + template: '
props from route config is: {{ msg }}
', + props: { + msg: { + type: String, + required: true + } + } +} + +// keep original values to restore them later +const originalSilent = Vue.config.silent +const originalWarnHandler = Vue.config.warnHandler + +const CatchWarn = { + template: `
{{ didWarn ? 'caught missing prop warn' : 'no missing prop warn' }}
`, + data () { + return { + didWarn: 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.didWarn = missPropWarn + }) + }, + beforeRouteLeave (to, from, next) { + // restore vue config + Vue.config.silent = originalSilent + Vue.config.warnHandler = originalWarnHandler + next() + } +} const router = new VueRouter({ mode: 'history', @@ -80,6 +125,24 @@ const router = new VueRouter({ ] } ] + }, + { + path: '/config-required-props', + component: Parent, + props: { msg: 'from parent' }, + children: [ + { + path: 'child', + component: RequiredProps, + props: { + msg: 'from child' + } + } + ] + }, + { + path: '/catch-warn', + component: CatchWarn } ] }) @@ -96,6 +159,9 @@ new Vue({
  • /with-guard2
  • /one/two/child1
  • /one/two/child2
  • +
  • /config-required-props
  • +
  • /config-required-props/child
  • +
  • /catch-warn
  • diff --git a/src/components/view.js b/src/components/view.js index 23d48b883..e24d6fa4e 100644 --- a/src/components/view.js +++ b/src/components/view.js @@ -39,17 +39,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) { + fillPropsinData(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 @@ -81,25 +96,37 @@ 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] + // save route and configProps in cachce + if (configProps) { + extend(cache[name], { + route, + configProps + }) + fillPropsinData(component, data, route, configProps) } return h(component, data, children) } } +function fillPropsinData (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 68a38ef48..59602848e 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', 7) + .assert.count('li a', 10) .click('li:nth-child(1) a') .assert.containsText('.view', 'index child1') @@ -44,6 +44,17 @@ module.exports = { .click('li:nth-child(7) a') .assert.containsText('.view', 'index child2') + // missing props in nested routes with keep alive + // https://github.com/vuejs/vue-router/issues/2301 + .click('li:nth-child(8) a') + .assert.containsText('.view', 'msg: from parent') + .click('li:nth-child(9) a') + .assert.containsText('.view', 'msg: from parent\nprops from route config is: from child') + .click('li:nth-child(10) a') + .assert.containsText('.view', 'no missing prop warn') + .click('li:nth-child(9) a') + .assert.containsText('.view', 'msg: from parent\nprops from route config is: from child') + .end() } }