Skip to content

Commit 5ad9d48

Browse files
committed
Feature: routeConfig.props - Passing props to a component
1 parent be7a138 commit 5ad9d48

File tree

12 files changed

+241
-0
lines changed

12 files changed

+241
-0
lines changed

docs/en/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- [Named Routes](essentials/named-routes.md)
1414
- [Named Views](essentials/named-views.md)
1515
- [Redirect and Alias](essentials/redirect-and-alias.md)
16+
- [Passing props](essentials/passing-props.md)
1617
- [HTML5 History Mode](essentials/history-mode.md)
1718
- Advanced
1819
- [Navigation Guards](advanced/navigation-guards.md)

docs/en/api/options.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
name?: string; // for named routes
1414
components?: { [name: string]: Component }; // for named views
1515
redirect?: string | Location | Function;
16+
props?: boolean | string | Function;
1617
alias?: string | Array<string>;
1718
children?: Array<RouteConfig>; // for nested routes
1819
beforeEnter?: (to: Route, from: Route, next: Function) => void;

docs/en/essentials/passing-props.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Passing props
2+
3+
Using `$route` in your component creates a tight coupling with the route which limits the flexibility of the component as it can only be used on certain urls.
4+
5+
To decouple this component from the router use props:
6+
7+
**❌ Coupled to $route**
8+
9+
``` js
10+
const User = {
11+
template: '<div>User {{ $route.params.id }}</div>'
12+
}
13+
const router = new VueRouter({
14+
routes: [
15+
{ path: '/user/:id', component: User }
16+
]
17+
})
18+
```
19+
20+
**👍 Decoupled with props**
21+
22+
``` js
23+
const User = {
24+
props: ['id'],
25+
template: '<div>User {{ id }}</div>'
26+
}
27+
const router = new VueRouter({
28+
routes: [
29+
{ path: '/user/:id', component: User, props: true }
30+
]
31+
})
32+
```
33+
34+
This allows you to use the component anywhere, which makes the component easier to reuse and test.
35+
36+
### Boolean mode
37+
38+
When props is set to true, the router looks at the component and searches for props with the same name as the route.params.
39+
When a prop is found and its type is Number, String or not set, the param is passed as prop.
40+
41+
### Object mode
42+
43+
When props is an object, this will be set as the component props as-is.
44+
45+
``` js
46+
const router = new VueRouter({
47+
routes: [
48+
{ path: '/user/first-one', component: User, props: {id: '1' } }
49+
]
50+
})
51+
```
52+
53+
### Function mode
54+
55+
You can create a function that returns props.
56+
This allows you to mix static values and values based on the route.
57+
58+
``` js
59+
const router = new VueRouter({
60+
routes: [
61+
{ path: '/search', component: SearchUser, props: (route) => ({ query: route.query.q }) }
62+
]
63+
})
64+
```
65+
66+
The url: `/search?q=vue` would pass `{query: "vue"}` as props to the SearchUser component.
67+
68+
Try to keep the props function stateless, as it's only evaluated on route changes.
69+
Tip: If you need to set props based on route and state create a wrapper component.
70+
71+
72+
For advanced usage, checkout the [example](https://github.com/vuejs/vue-router/blob/dev/examples/route-props/app.js).

examples/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ <h1>Vue Router Examples</h1>
1515
<li><a href="route-matching">Route Matching</a></li>
1616
<li><a href="active-links">Active Links</a></li>
1717
<li><a href="redirect">Redirect</a></li>
18+
<li><a href="route-props">Route Props</a></li>
1819
<li><a href="route-alias">Route Alias</a></li>
1920
<li><a href="transitions">Transitions</a></li>
2021
<li><a href="data-fetching">Data Fetching</a></li>

examples/route-props/Calculator.vue

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<template>
2+
<div>
3+
<h2 class="calculator_add">{{a}} + {{b}} = {{ a + b }}</h2>
4+
<h2 class="calculator_multiply">{{a}} * {{b}} = {{ a * b }}</h2>
5+
</div>
6+
</template>
7+
8+
<script>
9+
10+
export default {
11+
props: {
12+
a: {
13+
type: Number,
14+
required: true
15+
},
16+
b: {
17+
type: Number,
18+
required: true
19+
}
20+
}
21+
}
22+
</script>

examples/route-props/Hello.vue

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<template>
2+
<div>
3+
<h2 class="hello">Hello {{name}}</h2>
4+
</div>
5+
</template>
6+
7+
<script>
8+
9+
export default {
10+
props: {
11+
name: {
12+
type: String,
13+
default: 'Vue!'
14+
}
15+
}
16+
}
17+
</script>

examples/route-props/app.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import Vue from 'vue'
2+
import VueRouter from 'vue-router'
3+
import Hello from './Hello.vue'
4+
import Calculator from './Calculator.vue'
5+
6+
Vue.use(VueRouter)
7+
8+
function dynamicPropsFn (route) {
9+
const now = new Date()
10+
return {
11+
name: (now.getFullYear() + parseInt(route.params.years)) + '!'
12+
}
13+
}
14+
15+
const router = new VueRouter({
16+
mode: 'history',
17+
base: __dirname,
18+
routes: [
19+
{ path: '/', component: Hello }, // No props, no nothing
20+
{ path: '/hello/:name', component: Hello, props: true }, // Match params to props (Only matches props with type: String, Number or untyped)
21+
{ path: '/static', component: Hello, props: { name: 'world' }}, // static values
22+
{ path: '/dynamic/:years', component: Hello, props: dynamicPropsFn }, // custom logic for mapping between route and props
23+
{ path: '/calculator/:a/:b', component: Calculator, props: true } // Props with type Number are passed as number
24+
]
25+
})
26+
27+
new Vue({
28+
router,
29+
template: `
30+
<div id="app">
31+
<h1>Route props</h1>
32+
<ul>
33+
<li><router-link to="/">/</router-link></li>
34+
<li><router-link to="/hello/you">/hello/you</router-link></li>
35+
<li><router-link to="/static">/static</router-link></li>
36+
<li><router-link to="/dynamic/1">/dynamic/1</router-link></li>
37+
<li><router-link to="/calculator/2/3">/calculator/2/3</router-link></li>
38+
</ul>
39+
<router-view class="view"></router-view>
40+
</div>
41+
`
42+
}).$mount('#app')

examples/route-props/index.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<!DOCTYPE html>
2+
<link rel="stylesheet" href="/global.css">
3+
<a href="/">&larr; Examples index</a>
4+
<div id="app"></div>
5+
<script src="/__build__/shared.js"></script>
6+
<script src="/__build__/route-props.js"></script>

src/components/view.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { resolveProps } from '../util/props'
2+
13
export default {
24
name: 'router-view',
35
functional: true,
@@ -49,6 +51,7 @@ export default {
4951
matched.instances[name] = undefined
5052
}
5153
}
54+
data.props = resolveProps(route, component, matched.props && matched.props[name])
5255
}
5356

5457
return h(component, data, children)

src/create-route-map.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ function addRouteRecord (
4444
name,
4545
parent,
4646
matchAs,
47+
props: typeof route.props === 'undefined' ? {} : (route.components ? route.props : { default: route.props }),
4748
redirect: route.redirect,
4849
beforeEnter: route.beforeEnter,
4950
meta: route.meta || {}

src/util/props.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
2+
import { warn } from './warn'
3+
4+
export function resolveProps (route, component, config) {
5+
switch (typeof config) {
6+
7+
case 'undefined':
8+
return
9+
10+
case 'object':
11+
return config
12+
13+
case 'function':
14+
return config(route)
15+
16+
case 'boolean':
17+
if (!config) {
18+
return
19+
}
20+
if (!component.props) {
21+
return
22+
}
23+
const props = {}
24+
for (const prop in route.params) {
25+
const value = route.params[prop]
26+
if (hasProp(component, prop)) {
27+
const propType = component.props[prop].type
28+
if (propType === null || propType === String) {
29+
props[prop] = value
30+
} else if (propType === Number && value.match(/^-?([0-9]+|[0-9]*\.[0-9]+)$/)) {
31+
props[prop] = parseFloat(value)
32+
}
33+
}
34+
}
35+
return props
36+
default:
37+
warn(false, `props in "${route.path}" is a ${typeof config}, expecting an object, function or boolean.`)
38+
}
39+
}
40+
41+
function hasProp (component, prop) {
42+
if (!component.props) {
43+
return false
44+
}
45+
return typeof component.props[prop] !== 'undefined'
46+
}

test/e2e/specs/route-props.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
module.exports = {
2+
'route-props': function (browser) {
3+
browser
4+
.url('http://localhost:8080/route-props/')
5+
.waitForElementVisible('#app', 1000)
6+
.assert.count('li a', 5)
7+
8+
.assert.urlEquals('http://localhost:8080/route-props/')
9+
.assert.containsText('.hello', 'Hello Vue!')
10+
11+
.click('li:nth-child(2) a')
12+
.assert.urlEquals('http://localhost:8080/route-props/hello/you')
13+
.assert.containsText('.hello', 'Hello you')
14+
15+
.click('li:nth-child(3) a')
16+
.assert.urlEquals('http://localhost:8080/route-props/static')
17+
.assert.containsText('.hello', 'Hello world')
18+
19+
.click('li:nth-child(4) a')
20+
.assert.urlEquals('http://localhost:8080/route-props/dynamic/1')
21+
.assert.containsText('.hello', 'Hello ' + ((new Date()).getFullYear() + 1)+ '!')
22+
23+
.click('li:nth-child(5) a')
24+
.assert.urlEquals('http://localhost:8080/route-props/calculator/2/3')
25+
.assert.containsText('.calculator_add', '2 + 3 = 5')
26+
.assert.containsText('.calculator_multiply', '2 * 3 = 6')
27+
.end()
28+
}
29+
}

0 commit comments

Comments
 (0)