]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
feat: allow props for named views
authorEduardo San Martin Morote <posva13@gmail.com>
Tue, 26 May 2020 13:47:14 +0000 (15:47 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Tue, 26 May 2020 13:47:14 +0000 (15:47 +0200)
__tests__/RouterView.spec.ts
__tests__/matcher/records.spec.ts
__tests__/utils.ts
playground/router.ts
playground/views/Nested.vue
src/RouterView.ts
src/matcher/index.ts
src/matcher/types.ts
src/types/index.ts

index b7e163915dd1ed757f239e6b255738e0bb8ac1db..cce62e4e31d853669ff48eddce3eb8a2e25512cf 100644 (file)
@@ -3,7 +3,10 @@
  */
 import { RouterView } from '../src/RouterView'
 import { components, RouteLocationNormalizedLoose } from './utils'
-import { START_LOCATION_NORMALIZED } from '../src/types'
+import {
+  START_LOCATION_NORMALIZED,
+  RouteLocationNormalized,
+} from '../src/types'
 import { markRaw } from 'vue'
 import { mount, createMockedRoute } from './mount'
 import { mockWarn } from 'jest-mock-warn'
@@ -21,6 +24,8 @@ function createRoutes<T extends Record<string, RouteLocationNormalizedLoose>>(
   return nonReactiveRoutes
 }
 
+const props = { default: false }
+
 const routes = createRoutes({
   root: {
     fullPath: '/',
@@ -31,7 +36,12 @@ const routes = createRoutes({
     hash: '',
     meta: {},
     matched: [
-      { components: { default: components.Home }, instances: {}, path: '/' },
+      {
+        components: { default: components.Home },
+        instances: {},
+        path: '/',
+        props,
+      },
     ],
   },
   foo: {
@@ -43,7 +53,12 @@ const routes = createRoutes({
     hash: '',
     meta: {},
     matched: [
-      { components: { default: components.Foo }, instances: {}, path: '/foo' },
+      {
+        components: { default: components.Foo },
+        instances: {},
+        path: '/foo',
+        props,
+      },
     ],
   },
   nested: {
@@ -55,8 +70,18 @@ const routes = createRoutes({
     hash: '',
     meta: {},
     matched: [
-      { components: { default: components.Nested }, instances: {}, path: '/' },
-      { components: { default: components.Foo }, instances: {}, path: 'a' },
+      {
+        components: { default: components.Nested },
+        instances: {},
+        path: '/',
+        props,
+      },
+      {
+        components: { default: components.Foo },
+        instances: {},
+        path: 'a',
+        props,
+      },
     ],
   },
   nestedNested: {
@@ -68,9 +93,24 @@ const routes = createRoutes({
     hash: '',
     meta: {},
     matched: [
-      { components: { default: components.Nested }, instances: {}, path: '/' },
-      { components: { default: components.Nested }, instances: {}, path: 'a' },
-      { components: { default: components.Foo }, instances: {}, path: 'b' },
+      {
+        components: { default: components.Nested },
+        instances: {},
+        path: '/',
+        props,
+      },
+      {
+        components: { default: components.Nested },
+        instances: {},
+        path: 'a',
+        props,
+      },
+      {
+        components: { default: components.Foo },
+        instances: {},
+        path: 'b',
+        props,
+      },
     ],
   },
   named: {
@@ -82,7 +122,7 @@ const routes = createRoutes({
     hash: '',
     meta: {},
     matched: [
-      { components: { foo: components.Foo }, instances: {}, path: '/' },
+      { components: { foo: components.Foo }, instances: {}, path: '/', props },
     ],
   },
   withParams: {
@@ -99,7 +139,7 @@ const routes = createRoutes({
 
         instances: {},
         path: '/users/:id',
-        props: true,
+        props: { default: true },
       },
     ],
   },
@@ -117,7 +157,7 @@ const routes = createRoutes({
 
         instances: {},
         path: '/props/:id',
-        props: { id: 'foo', other: 'fixed' },
+        props: { default: { id: 'foo', other: 'fixed' } },
       },
     ],
   },
@@ -136,7 +176,12 @@ const routes = createRoutes({
 
         instances: {},
         path: '/props/:id',
-        props: to => ({ id: Number(to.params.id) * 2, other: to.query.q }),
+        props: {
+          default: (to: RouteLocationNormalized) => ({
+            id: Number(to.params.id) * 2,
+            other: to.query.q,
+          }),
+        },
       },
     ],
   },
@@ -203,6 +248,7 @@ describe('RouterView', () => {
           components: { default: components.User },
           instances: {},
           path: '/users/:id',
+          props,
         },
       ],
     }
index aa25b11545e1e8e17503d9ac38dc9e6c326291da..a49f901950dd12966c4edcf31747ad96e6fdf9c8 100644 (file)
@@ -17,7 +17,7 @@ describe('normalizeRouteRecord', () => {
       meta: {},
       name: undefined,
       path: '/home',
-      props: false,
+      props: { default: false },
     })
   })
 
@@ -41,7 +41,7 @@ describe('normalizeRouteRecord', () => {
       meta: { foo: true },
       name: 'name',
       path: '/home',
-      props: false,
+      props: { default: false },
     })
   })
 
@@ -83,7 +83,7 @@ describe('normalizeRouteRecord', () => {
       meta: { foo: true },
       name: 'name',
       path: '/home',
-      props: false,
+      props: { one: false },
     })
   })
 })
index 225eca37c3c4c982140e23cca3b37660ba2ad82d..d93270fc2f35527beb5fd24dba614f360ab34bef 100644 (file)
@@ -8,6 +8,7 @@ import {
   RouteComponent,
   RouteRecordRaw,
   RouteRecordName,
+  _RouteRecordProps,
 } from '../src/types'
 import { h, ComponentOptions } from 'vue'
 import {
@@ -52,7 +53,7 @@ export interface RouteRecordViewLoose
   > {
   leaveGuards?: any
   instances: Record<string, any>
-  props?: _RouteRecordBase['props']
+  props: Record<string, _RouteRecordProps>
   aliasOf: RouteRecordViewLoose | undefined
 }
 
index b2a89f4ac251fd02b7bdd295e13394694b48e876..2b030a548145457580c25805e136429cfedb5ec6 100644 (file)
@@ -25,7 +25,7 @@ export const router = createRouter({
     {
       path: '/',
       components: { default: Home, other: component },
-      props: to => ({ waited: to.meta.waitedFor }),
+      props: { default: to => ({ waited: to.meta.waitedFor }) },
     },
     {
       path: '/always-redirect',
@@ -60,18 +60,18 @@ export const router = createRouter({
     {
       path: '/children',
       name: 'WithChildren',
-      component,
+      component: Nested,
       children: [
-        { path: '', alias: 'alias', name: 'default-child', component },
-        { path: 'a', name: 'a-child', component },
+        { path: '', alias: 'alias', name: 'default-child', component: Nested },
+        { path: 'a', name: 'a-child', component: Nested },
         {
           path: 'b',
           name: 'b-child',
-          component,
+          component: Nested,
           children: [
-            { path: '', component },
-            { path: 'a2', component },
-            { path: 'b2', component },
+            { path: '', component: Nested },
+            { path: 'a2', component: Nested },
+            { path: 'b2', component: Nested },
           ],
         },
       ],
index 654a83884204e120236650187891a99f78644db4..d1a337a32c59c59916efac6a7470b3c6ecae92f3 100644 (file)
@@ -1,7 +1,7 @@
 <template>
   <div>
     <p>Nested level {{ level }}</p>
-    <ul v-if="level === 1">
+    <ul v-if="level === 1 && $route.name === 'Nested'">
       <li>
         <router-link to="/nested/nested">/nested/nested</router-link>
       </li>
index 91ddcb094c6e2ede7ce7e85b02353bd347ae07cc..fb7fda9cc0b26bf2da981df1ef9e8e606bd893b9 100644 (file)
@@ -52,11 +52,14 @@ export const RouterViewImpl = defineComponent({
     const propsData = computed(() => {
       // propsData only gets called if ViewComponent.value exists and it depends
       // on matchedRoute.value
-      const { props } = matchedRoute.value!
-      if (!props) return {}
-      if (props === true) return route.value.params
+      const componentProps = matchedRoute.value!.props[props.name]
+      if (!componentProps) return {}
+      // TODO: only add props declared in the component. all if no props
+      if (componentProps === true) return route.value.params
 
-      return typeof props === 'object' ? props : props(route.value)
+      return typeof componentProps === 'object'
+        ? componentProps
+        : componentProps(route.value)
     })
 
     provide(matchedRouteKey, matchedRoute)
index de894a29e3d28b831831600e02973de616853af2..dae90ca3ed3a997218be727791d5600baa64072f 100644 (file)
@@ -4,6 +4,9 @@ import {
   MatcherLocation,
   isRouteName,
   RouteRecordName,
+  _RouteRecordProps,
+  RouteRecordSingleView,
+  RouteRecordMultipleViews,
 } from '../types'
 import { createRouterError, ErrorTypes, MatcherError } from '../errors'
 import { createRouteRecordMatcher, RouteRecordMatcher } from './pathMatcher'
@@ -321,22 +324,43 @@ export function normalizeRouteRecord(
       redirect: record.redirect,
     }
   } else {
+    const components =
+      'components' in record ? record.components : { default: record.component }
     return {
       ...commonInitialValues,
       beforeEnter: record.beforeEnter,
-      props: record.props || false,
+      props: normalizeRecordProps(record),
       children: record.children || [],
       instances: {},
       leaveGuards: [],
       updateGuards: [],
-      components:
-        'components' in record
-          ? record.components
-          : { default: record.component },
+      components,
     }
   }
 }
 
+/**
+ * Normalize the optional `props` in a record to always be an object similar to
+ * components. Also accept a boolean for components.
+ * @param record
+ */
+function normalizeRecordProps(
+  record: RouteRecordSingleView | RouteRecordMultipleViews
+): Record<string, _RouteRecordProps> {
+  const propsObject = {} as Record<string, _RouteRecordProps>
+  const props = record.props || false
+  if ('component' in record) {
+    propsObject.default = props
+  } else {
+    // NOTE: we could also allow a function to be applied to every component.
+    // Would need user feedback for use cases
+    for (let name in record.components)
+      propsObject[name] = typeof props === 'boolean' ? props : props[name]
+  }
+
+  return propsObject
+}
+
 /**
  * Checks if a record or any of its parent is an alias
  * @param record
index 8ce537a1c7a56ef622ad50bf338790fda80584bb..67b8e9806407c234d522f1e6152ee499fc403edc 100644 (file)
@@ -3,6 +3,7 @@ import {
   NavigationGuard,
   _RouteRecordBase,
   RouteRecordRedirectRaw,
+  _RouteRecordProps,
 } from '../types'
 import { ComponentPublicInstance } from 'vue'
 
@@ -13,7 +14,10 @@ export interface RouteRecordNormalized {
   components: RouteRecordMultipleViews['components']
   children: Exclude<RouteRecordMultipleViews['children'], void>
   meta: Exclude<RouteRecordMultipleViews['meta'], void>
-  props: Exclude<_RouteRecordBase['props'], void>
+  /**
+   * Object of props options with the same keys as `components`
+   */
+  props: Record<string, _RouteRecordProps>
   beforeEnter: RouteRecordMultipleViews['beforeEnter']
   leaveGuards: NavigationGuard[]
   updateGuards: NavigationGuard[]
index ca62b34dd851ddc28c8cba26d62532904d59faaa..1f0911841e0e164abbaa2b02143ff082ec9f616f 100644 (file)
@@ -140,6 +140,14 @@ export type RawRouteComponent = RouteComponent | Lazy<RouteComponent>
 
 export type RouteRecordName = string | symbol
 
+/**
+ * @internal
+ */
+export type _RouteRecordProps =
+  | boolean
+  | Record<string, any>
+  | ((to: RouteLocationNormalized) => Record<string, any>)
+
 // TODO: could this be moved to matcher?
 /**
  * Common properties among all kind of {@link RouteRecordRaw}
@@ -160,13 +168,6 @@ export interface _RouteRecordBase extends PathParserOptions {
    * Name for the route record.
    */
   name?: RouteRecordName
-  /**
-   * Allow passing down params as props to the component rendered by `router-view`.
-   */
-  props?:
-    | boolean
-    | Record<string, any>
-    | ((to: RouteLocationNormalized) => Record<string, any>)
   /**
    * Before Enter guard specific to this record. Note `beforeEnter` has no
    * effect if the record has a `redirect` property.
@@ -193,11 +194,21 @@ export interface RouteRecordRedirectRaw extends _RouteRecordBase {
 export interface RouteRecordSingleView extends _RouteRecordBase {
   component: RawRouteComponent
   children?: RouteRecordRaw[]
+  /**
+   * Allow passing down params as props to the component rendered by `router-view`.
+   */
+  props?: _RouteRecordProps
 }
 
 export interface RouteRecordMultipleViews extends _RouteRecordBase {
   components: Record<string, RawRouteComponent>
   children?: RouteRecordRaw[]
+  /**
+   * Allow passing down params as props to the component rendered by
+   * `router-view`. Should be an object with the same keys as `components` or a
+   * boolean to be applied to every component.
+   */
+  props?: Record<string, _RouteRecordProps> | boolean
 }
 
 export type RouteRecordRaw =