对初学者来说,使用路由后遇到的第一个问题通常是尝试在路由跳转时执行定义的函数,但是发现路由跳转时函数没有执行,类似下面这样的代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue-router</title>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
</head>
<body>
<div id="app">
<p>
<router-link @click="routerChange" to="/page1">Go to Page1</router-link>
<router-link @click="routerChange" to="/page2">Go to Page2</router-link>
</p>
<router-view></router-view>
</div>
<script>
const Page1 = { template: '<div>page1</div>' };
const Page2 = { template: '<div>page2</div>' };
const routes = [
{ path: '/page1', component: Page1 },
{ path: '/page2', component: Page2 }
];
const router = new VueRouter({
routes
});
new Vue({
router,
el: '#app',
methods: {
routerChange: () => {
console.log("enter router change.");
}
}
});
</script>
</body>
</html>
本意是想在路由跳转时执行routerChange函数,但是可以看到控制台什么信息都没有打印:
下面先说一下问题怎么解决:在路由的响应事件上加上native修饰符就可以解决问题。代码变更很小(12行和13行):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue-router</title>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
</head>
<body>
<div id="app">
<p>
<router-link @click.native="routerChange" to="/page1">Go to Page1</router-link>
<router-link @click.native="routerChange" to="/page2">Go to Page2</router-link>
</p>
<router-view></router-view>
</div>
<script>
const Page1 = { template: '<div>page1</div>' };
const Page2 = { template: '<div>page2</div>' };
const routes = [
{ path: '/page1', component: Page1 },
{ path: '/page2', component: Page2 }
];
const router = new VueRouter({
routes
});
new Vue({
router,
el: '#app',
methods: {
routerChange: () => {
console.log("enter router change.");
}
}
});
</script>
</body>
</html>
再次触发路由变更,可以看到控制台打印了“enter router change.”,这表明routerChange函数被正常执行了:
接下来说明一下问题原因,官网对native修饰符的使用场景说明是:在一个组件的根元素上直接监听一个原生事件。这样看来,router-link也是一个组件,下图的dom结构也可以看出来,router解析以后其实是一个a标签:
在没有加native修饰符时,router-link组件无法预知我们会定义什么事件,当然也就不会通过$emit通知父元素执行click事件定义的函数。
下面扩展一下组件的使用场景,如果组件的根元素没有对应的原生事件,即使我们加上了native修饰符,响应事件对应的函数也不会执行。先上代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>component</title>
<script src="https://cdn.bootcss.com/vue/2.6.10/vue.js"></script>
</head>
<body>
<div id="app">
<my-input @focus.native="routerChange"></my-input>
<my-input @click.native="routerChange"></my-input>
</div>
<script>
Vue.component('my-input', {
template: '<label><input type="text" ref="input" :value="value" /></label>',
data: function () {
return {
value: "default value"
};
}
});
new Vue({
el: '#app',
methods: {
routerChange: () => {
console.log("enter router change.");
}
}
});
</script>
</body>
</html>
点击第一个输入框,控制台没有打印任何信息,点击第二个输入框,控制台打印了“enter router change.”,即对于组件的根元素label来说,没有focus事件,但是有click事件:
对于开发者来说,使用native修饰符时必须明确需要监听的是根元素的原生事件。
最后再扩展一点,如果需要监听的是上例中input元素的focus事件(非组件根元素的事件),也还有办法实现的(当然笔者不太喜欢这种编程风格,对于父元素来说,不应该关心组件内容是什么结构,万一组件dom需要重构呢?监听组件根元素这种已经是底线了,当然,应用在组件多层嵌套的情况是可以接受的。),可以通过$attrs和$listeners来传递参数和方法,下面给出一个组件多层嵌套的例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>component</title>
<script src="https://cdn.bootcss.com/vue/2.6.10/vue.js"></script>
</head>
<body>
<div id="app">
<input type="text" @input="function1" />
<br><br>
<com-a :param1="param1" :param2="param2" :param3="param3" @function2="function2"
@function3="function3"></com-a>
</div>
<script>
Vue.component('com-a', {
template: '<div style="width: 100px; height: 100px; background-color: gray;" ' +
'@click="clickItem"><com-b v-bind="$attrs" v-on="subListener"></com-b></div>',
props: ['param1'],
computed: {
subListener: function () {
return Object.assign({}, {
function3: this.$listeners.function3
});
}
},
inheritAttrs: false,
created () {
console.log(this.$attrs);
console.log(this.$listeners);
},
methods: {
clickItem: function () {
this.$listeners.function2();
}
}
});
Vue.component('com-b', {
template: '<div style="width: 80px; height: 80px; background-color: rebeccapurple;"' +
'@click="clickItem"></div>',
props: ['param2'],
inheritAttrs: false,
created () {
console.log(this.$attrs);
console.log(this.$listeners);
},
methods: {
clickItem: function () {
this.$listeners.function3();
}
}
});
new Vue({
el: '#app',
data () {
return {
param1: "param1",
param2: "param2",
param3: "param3"
}
},
methods: {
function1: function () {
console.log("enter function1.");
},
function2: function () {
console.log("enter function2.");
},
function3: function () {
console.log("enter function3.");
}
}
});
</script>
</body>
</html>
初始化的时候组件com-a接收到了2个父元素传入的参数和2个父元素传入的函数,组件com-b接收到了1个父元素传入的参数和1个父元素传入的函数;
当点击组件com-a的区域(与com-b不重叠)时,传入的function2正常执行;
当点击组件com-a的区域(与com-b重叠)时,传入的function2和function3正常执行(function2在组件com-a内执行,function3在组件com-b内执行,function2能执行是由于组件com-b没有阻止点击事件的冒泡)。
通过上面这种方式,就可以把外层的参数和方法传入内部嵌套的组件内。常用的应用场景是:
外层的父元素需要往内层嵌套的组件传参或函数,但在不同的页面可能需要嵌套不同的组件,这些组件的参数信息和函数信息也可能不同,而中间层的组件并不想感知嵌套组件需要什么样的参数或函数,那么用$attrs和$listeners无疑是合适的。
就上例来说,组件com-a如果直接传递this.$listeners给组件com-b,则对于组件com-a(此时组件com-a就相当于是中间层组件)来说,完全不需要感知$attrs和$listeners包含了什么属性和方法(例子只是为了尽可能包含多种情况,去解析了一把$listeners)。
针对上例的补充说明:
inheritAttrs参数用于控制传入的参数是否以属性的方式加在组件的根元素上。
当组件com-b的inheritAttrs设置为true时,生成的dom(组件根元素包含param3属性):
当组件com-b的inheritAttrs设置为false时,生成的dom(组件根元素不包含param3属性):
$attrs中只会包含组件props中未定义的参数。
如上例中向组件com-a传入了param1、param2和param3共3个参数,组件com-a的props只声明了param1一个参数,则其余的2个param2和param3都会包含在组件com-a的$attrs中: