import { warn } from '../util/warn' import { extend } from '../util/misc' import { handleRouteEntered } from '../util/route' export default { name: 'RouterView', functional: true, props: { name: { type: String, default: 'default' } }, render (_, { props, children, parent, data }) { // used by devtools to display a router-view badge data.routerView = true // directly use parent context's createElement() function // so that components rendered by router-view can resolve named slots const h = parent.$createElement const name = props.name const route = parent.$route const cache = parent._routerViewCache || (parent._routerViewCache = {}) // determine current view depth, also check to see if the tree // has been toggled inactive but kept-alive. let depth = 0 let inactive = false while (parent && parent._routerRoot !== parent) { const vnodeData = parent.$vnode ? parent.$vnode.data : {} if (vnodeData.routerView) { depth++ } if (vnodeData.keepAlive && parent._directInactive && parent._inactive) { inactive = true } parent = parent.$parent } data.routerViewDepth = depth // render previous view if the tree is inactive and kept-alive if (inactive) { 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] 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() } // cache component cache[name] = { component } // attach instance registration hook // this will be called in the instance's injected lifecycle hooks data.registerRouteInstance = (vm, val) => { // val could be undefined for unregistration const current = matched.instances[name] if ( (val && current !== vm) || (!val && current === vm) ) { matched.instances[name] = val } } // also register instance in prepatch hook // in case the same component instance is reused across different routes ;(data.hook || (data.hook = {})).prepatch = (_, vnode) => { matched.instances[name] = vnode.componentInstance } // register instance in init hook // in case kept-alive component be actived when routes changed data.hook.init = (vnode) => { if (vnode.data.keepAlive && vnode.componentInstance && vnode.componentInstance !== matched.instances[name] ) { matched.instances[name] = vnode.componentInstance } // if the route transition has already been confirmed then we weren't // able to call the cbs during confirmation as the component was not // registered yet, so we call it here. handleRouteEntered(route) } const configProps = matched.props && matched.props[name] // save route and configProps in cache 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': return case 'object': return config case 'function': return config(route) case 'boolean': return config ? route.params : undefined default: if (process.env.NODE_ENV !== 'production') { warn( false, `props in "${route.path}" is a ${typeof config}, ` + `expecting an object, function or boolean.` ) } } }