-
-
Notifications
You must be signed in to change notification settings - Fork 5k
New feature: Passing props to components from vue-router #973
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
be7a138
5ad9d48
0902926
608f6e3
5ff882e
a7cd827
0ac46ca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
# Passing props | ||
|
||
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. | ||
|
||
To decouple this component from the router use props: | ||
|
||
**❌ Coupled to $route** | ||
|
||
``` js | ||
const User = { | ||
template: '<div>User {{ $route.params.id }}</div>' | ||
} | ||
const router = new VueRouter({ | ||
routes: [ | ||
{ path: '/user/:id', component: User } | ||
] | ||
}) | ||
``` | ||
|
||
**👍 Decoupled with props** | ||
|
||
``` js | ||
const User = { | ||
props: ['id'], | ||
template: '<div>User {{ id }}</div>' | ||
} | ||
const router = new VueRouter({ | ||
routes: [ | ||
{ path: '/user/:id', component: User, props: true } | ||
] | ||
}) | ||
``` | ||
|
||
This allows you to use the component anywhere, which makes the component easier to reuse and test. | ||
|
||
### Boolean mode | ||
|
||
When props is set to true, the router looks at the component and searches for props with the same name as the route.params. | ||
When a prop is found and its type is Number, String or not set, the param is passed as prop. | ||
|
||
### Object mode | ||
|
||
When props is an object, this will be set as the component props as-is. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It'd be even better to say when it's useful to pass an object as the props There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've improved the description and code snippet. |
||
|
||
``` js | ||
const router = new VueRouter({ | ||
routes: [ | ||
{ path: '/user/first-one', component: User, props: {id: '1' } } | ||
] | ||
}) | ||
``` | ||
|
||
### Function mode | ||
|
||
You can create a function that returns props. | ||
This allows you to mix static values and values based on the route. | ||
|
||
``` js | ||
const router = new VueRouter({ | ||
routes: [ | ||
{ path: '/search', component: SearchUser, props: (route) => ({ query: route.query.q }) } | ||
] | ||
}) | ||
``` | ||
|
||
The url: `/search?q=vue` would pass `{query: "vue"}` as props to the SearchUser component. | ||
|
||
Try to keep the props function stateless, as it's only evaluated on route changes. | ||
Tip: If you need to set props based on route and state create a wrapper component. | ||
|
||
|
||
For advanced usage, checkout the [example](https://github.com/vuejs/vue-router/blob/dev/examples/route-props/app.js). |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<template> | ||
<div> | ||
<h2 class="calculator_add">{{a}} + {{b}} = {{ a + b }}</h2> | ||
<h2 class="calculator_multiply">{{a}} * {{b}} = {{ a * b }}</h2> | ||
</div> | ||
</template> | ||
|
||
<script> | ||
|
||
export default { | ||
props: { | ||
a: { | ||
type: Number, | ||
required: true | ||
}, | ||
b: { | ||
type: Number, | ||
required: true | ||
} | ||
} | ||
} | ||
</script> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<template> | ||
<div> | ||
<h2 class="hello">Hello {{name}}</h2> | ||
</div> | ||
</template> | ||
|
||
<script> | ||
|
||
export default { | ||
props: { | ||
name: { | ||
type: String, | ||
default: 'Vue!' | ||
} | ||
} | ||
} | ||
</script> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import Vue from 'vue' | ||
import VueRouter from 'vue-router' | ||
import Hello from './Hello.vue' | ||
import Calculator from './Calculator.vue' | ||
|
||
Vue.use(VueRouter) | ||
|
||
function dynamicPropsFn (route) { | ||
const now = new Date() | ||
return { | ||
name: (now.getFullYear() + parseInt(route.params.years)) + '!' | ||
} | ||
} | ||
|
||
const router = new VueRouter({ | ||
mode: 'history', | ||
base: __dirname, | ||
routes: [ | ||
{ path: '/', component: Hello }, // No props, no nothing | ||
{ path: '/hello/:name', component: Hello, props: true }, // Match params to props (Only matches props with type: String, Number or untyped) | ||
{ path: '/static', component: Hello, props: { name: 'world' }}, // static values | ||
{ path: '/dynamic/:years', component: Hello, props: dynamicPropsFn }, // custom logic for mapping between route and props | ||
{ path: '/calculator/:a/:b', component: Calculator, props: true } // Props with type Number are passed as number | ||
] | ||
}) | ||
|
||
new Vue({ | ||
router, | ||
template: ` | ||
<div id="app"> | ||
<h1>Route props</h1> | ||
<ul> | ||
<li><router-link to="/">/</router-link></li> | ||
<li><router-link to="/hello/you">/hello/you</router-link></li> | ||
<li><router-link to="/static">/static</router-link></li> | ||
<li><router-link to="/dynamic/1">/dynamic/1</router-link></li> | ||
<li><router-link to="/calculator/2/3">/calculator/2/3</router-link></li> | ||
</ul> | ||
<router-view class="view"></router-view> | ||
</div> | ||
` | ||
}).$mount('#app') |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<!DOCTYPE html> | ||
<link rel="stylesheet" href="/global.css"> | ||
<a href="/">← Examples index</a> | ||
<div id="app"></div> | ||
<script src="/__build__/shared.js"></script> | ||
<script src="/__build__/route-props.js"></script> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
import { resolveProps } from '../util/props' | ||
|
||
export default { | ||
name: 'router-view', | ||
functional: true, | ||
|
@@ -49,6 +51,7 @@ export default { | |
matched.instances[name] = undefined | ||
} | ||
} | ||
data.props = resolveProps(route, component, matched.props && matched.props[name]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Edit: Sorry, I was wrong about that.. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The props of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did some further testing and setting properties on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, that's normal Vue behaviour for any component. |
||
} | ||
|
||
return h(component, data, children) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
|
||
import { warn } from './warn' | ||
|
||
export function resolveProps (route, component, config) { | ||
switch (typeof config) { | ||
|
||
case 'undefined': | ||
return | ||
|
||
case 'object': | ||
return config | ||
|
||
case 'function': | ||
return config(route) | ||
|
||
case 'boolean': | ||
if (!config) { | ||
return | ||
} | ||
if (!component.props) { | ||
return | ||
} | ||
const props = {} | ||
for (const prop in route.params) { | ||
const value = route.params[prop] | ||
if (hasProp(component, prop)) { | ||
const propType = component.props[prop].type | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure about reading prop types at runtime and converting them to Also, if this type conversion were to be supported, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not ideal, but the alternative was either not to support Number type, or pass it as an string and get a propType warnings and subtle bugs in I think routing to ID is a common use-case to allow it in the {props: true} scenario There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe in that case, the user should provide a function and convert types by himself. This way we avoid the implicit behavior. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've simplified the behavior of the {props: true} which now sets the props to route.params. |
||
if (propType === null || propType === String) { | ||
props[prop] = value | ||
} else if (propType === Number && value.match(/^-?([0-9]+|[0-9]*\.[0-9]+)$/)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should probably cache this regex literal There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, done |
||
props[prop] = parseFloat(value) | ||
} | ||
} | ||
} | ||
return props | ||
default: | ||
warn(false, `props in "${route.path}" is a ${typeof config}, expecting an object, function or boolean.`) | ||
} | ||
} | ||
|
||
function hasProp (component, prop) { | ||
if (!component.props) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can probably skip this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, i've inlined the hasProp function |
||
return false | ||
} | ||
return typeof component.props[prop] !== 'undefined' | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,7 @@ | ||
// http://nightwatchjs.org/guide#settings-file | ||
var seleniumServer = require('selenium-server'); | ||
var chromedriver = require('chromedriver'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line is not necessary There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lines are removed |
||
|
||
module.exports = { | ||
'src_folders': ['test/e2e/specs'], | ||
'output_folder': 'test/e2e/reports', | ||
|
@@ -7,11 +10,11 @@ module.exports = { | |
|
||
'selenium': { | ||
'start_process': true, | ||
'server_path': 'node_modules/selenium-server/lib/runner/selenium-server-standalone-2.53.1.jar', | ||
'server_path': seleniumServer.path, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be consistent we should do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
'host': '127.0.0.1', | ||
'port': 4444, | ||
'cli_args': { | ||
'webdriver.chrome.driver': 'node_modules/chromedriver/lib/chromedriver/chromedriver' | ||
'webdriver.chrome.driver': chromedriver.path | ||
} | ||
}, | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
module.exports = { | ||
'route-props': function (browser) { | ||
browser | ||
.url('http://localhost:8080/route-props/') | ||
.waitForElementVisible('#app', 1000) | ||
.assert.count('li a', 5) | ||
|
||
.assert.urlEquals('http://localhost:8080/route-props/') | ||
.assert.containsText('.hello', 'Hello Vue!') | ||
|
||
.click('li:nth-child(2) a') | ||
.assert.urlEquals('http://localhost:8080/route-props/hello/you') | ||
.assert.containsText('.hello', 'Hello you') | ||
|
||
.click('li:nth-child(3) a') | ||
.assert.urlEquals('http://localhost:8080/route-props/static') | ||
.assert.containsText('.hello', 'Hello world') | ||
|
||
.click('li:nth-child(4) a') | ||
.assert.urlEquals('http://localhost:8080/route-props/dynamic/1') | ||
.assert.containsText('.hello', 'Hello ' + ((new Date()).getFullYear() + 1)+ '!') | ||
|
||
.click('li:nth-child(5) a') | ||
.assert.urlEquals('http://localhost:8080/route-props/calculator/2/3') | ||
.assert.containsText('.calculator_add', '2 + 3 = 5') | ||
.assert.containsText('.calculator_multiply', '2 * 3 = 6') | ||
.end() | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is
Boolean
|Object
|Function
in passing-props.md