导航

props 显示声明,都会存放进子组件的 this.$attrs 中
$emit 触发App.vue
<template>
<div>
<my-selector
:value="selectorValue"
@changeOption="changeOption"
></my-selector>
</div>
</template>
<script>
import MySelector from './components/MySelector.vue';
export default {
name: 'App',
components: {
MySelector
},
data() {
return {
selectorValue: '3'
}
},
methods: {
changeOption(val) {
console.log(val);
}
}
}
</script>
MySelector.vue
<template>
<select
v-model="selectValue"
@change="changeOption"
>
<option value="1">选项1</option>
<option value="2">选项2</option>
<option value="3">选项3</option>
</select>
</template>
<script>
export default {
name: 'MySelector',
props: {
value: {
type: String,
default: '1'
}
},
data() {
return {
selectValue: this.value
}
},
methods: {
changeOption() {
this.$emit('changeOption', this.selectValue);
}
}
}
</script>
我们删除父组件的自定义事件绑定以及子组件的 emit 监听,只传值:
App.vue
<template>
<div>
<my-selector
:value="selectorValue"
model="123"
id="mySelector"
class="my-selector"
></my-selector>
</div>
</template>
<script>
import MySelector from './components/MySelector.vue';
export default {
name: 'App',
components: {
MySelector
},
data() {
return {
selectorValue: '3'
}
},
}
</script>
MySelector.vue
<template>
<!-- <select
v-model="selectValue"
@change="changeOption"
> -->
<select>
<option value="1">选项1</option>
<option value="2">选项2</option>
<option value="3">选项3</option>
</select>
</template>
<script>
export default {
name: 'MySelector',
// props: {
// value: {
// type: String,
// default: '1'
// }
// },
// data() {
// return {
// selectValue: this.value
// }
// },
}
</script>
我们此刻会发现,页面上默认vue就帮我们把父传子的值传过来了,而且给组件 <my-selector> 绑定的 model 属性,也自动传给了组件内部的 select 标签:

我们可以验证下标题的结论:
MySelector.vue
<template>
<select>
<option value="1">选项1</option>
<option value="2">选项2</option>
<option value="3">选项3</option>
</select>
</template>
<script>
export default {
name: 'MySelector',
created() {
console.log(this.$attrs);
}
}
</script>

App.vue
<template>
<div>
<my-selector
:value="selectorValue"
model="123"
id="mySelector"
class="my-selector"
@change="changeOption"
></my-selector>
</div>
</template>
<script>
import MySelector from './components/MySelector.vue';
export default {
name: 'App',
components: {
MySelector
},
data() {
return {
selectorValue: '3'
}
},
methods: {
changeOption(e) {
console.log(e.target.value); // 子组件不监听 @change 事件,此处也能监听到
}
}
}
</script>

MySelector.vue
<template>
<select>
<option value="1">选项1</option>
<option value="2">选项2</option>
<option value="3">选项3</option>
</select>
</template>
<script>
export default {
name: 'MySelector',
inheritAttrs: false, // 禁用根组件 select 继承父组件传递过来的属性
created() {
console.log(this.$attrs);
}
}
</script>
这样标签就不会继承属性了,切换selector也没法触发事件:

this.$attrs 依然有值:
如果我们此时手动给 select 标签绑定 $attrs (平铺属性),就又能继承了(事件也能生效):
<template>
<select v-bind="$attrs">
<option value="1">选项1</option>
<option value="2">选项2</option>
<option value="3">选项3</option>
</select>
</template>
...

想要实现让子组件 input 聚焦:
LoginBox.vue
<template>
<div>
<input type="text" placeholder="Username">
<input type="password" placeholder="Password">
<button>Login</button>
</div>
</template>
<script>
export default {
name: 'LoginBox'
}
</script>
App.vue
<template>
<div>
<my-selector
:value="selectorValue"
model="123"
id="mySelector"
class="my-selector"
@change="changeOption"
></my-selector>
<login-box autofocus></login-box>
</div>
</template>
<script>
import MySelector from './components/MySelector.vue';
import LoginBox from './components/LoginBox.vue';
export default {
name: 'App',
components: {
MySelector,
LoginBox
},
...
}
</script>
我们会发现 input 并没有聚焦,而是把 autofocus 属性设置给了组件的根元素 div:

此时我们就需要禁止默认继承来达到目的:
<template>
<div>
<input type="text" :autofocus="$attrs.autofocus" placeholder="Username">
<!-- 或者 -->
<input type="text" v-bind="$attrs" placeholder="Username">
<input type="password" placeholder="Password">
<button>Login</button>
</div>
</template>
<script>
export default {
inheritAttrs: false, // 其实不写这个,上边的 :autofocus="$attrs.autofocus" 也能生效
name: 'LoginBox'
}
</script>

App.vue
<template>
<div>
<my-selector
:value="selectorValue"
model="123"
id="mySelector"
class="my-selector"
@change="changeOption"
></my-selector>
<login-box autofocus></login-box>
<my-list-container :listTitle="listTitle">
<my-list :myList="myList" data-show="123" @click="bookSubscribe"></my-list>
</my-list-container>
</div>
</template>
<script>
import MySelector from './components/MySelector.vue';
import LoginBox from './components/LoginBox.vue';
import MyList from './components/MyList.vue';
import MyListContainer from './components/MyListContainer.vue';
export default {
name: 'App',
components: {
MySelector,
LoginBox,
MyList,
MyListContainer
},
data() {
return {
selectorValue: '3',
listTitle: '小说订阅列表',
myList: [
{
id: 1,
title: '悲惨世界',
isSubscribable: true,
subscribed: false
},
{
id: 2,
title: '傲慢与偏见',
isSubscribable: false,
subscribed: false
},
{
id: 3,
title: '呼啸山庄',
isSubscribable: true,
subscribed: false
}
]
}
},
methods: {
changeOption(e) {
console.log(e.target.value);
},
bookSubscribe(e) {
const id = e.target.dataset.id;
this.myList = this.myList.map(item => {
if (item.id == id) {
item.subscribed = !item.subscribed;
}
return item;
});
}
}
}
</script>
<style>
</style>
MyList.vue
v-if="item.isSubscribable" 为真,才会给 li 元素绑定上 v-bind="$attrs"@click="bookSubscribe" 绑定到 li 元素上去<template>
<!-- 多个根组件,n个li元素 -->
<template
v-for="item of myList">
<li
v-if="item.isSubscribable"
:key="item.id"
v-bind="$attrs"
>
<span>{{ item.title }}</span>
<button :data-id="item.id">{{ item.subscribed ? '已订阅' : '订阅' }}</button>
</li>
<li v-else :key="item.id">
<span>{{ item.title }}</span>
<button disabled>不可订阅</button>
</li>
</template>
</template>
<script>
export default {
name: 'MyList',
props: {
myList: Array
},
created() {
console.log(this.$attrs);
}
}
</script>
基础组件透传(保持所有原生能力)
<!-- 封装增强型 el-input 组件 -->
<template>
<div class="custom-wrapper">
<el-input
v-bind="$attrs" <!-- 透传所有属性 -->
v-on="$listeners" <!-- 透传所有事件 -->
>
<!-- 保留原有插槽 -->
<template v-for="(_, slotName) in $slots" :slot="slotName">
<slot :name="slotName"/>
</template>
</el-input>
<div class="extra-info">输入字符数:{{ value?.length || 0 }}</div>
</div>
</template>
<script>
export default {
// 显式声明需要特殊处理的 prop
props: ['value']
}
</script>
使用效果:
el-input 一样使用该组件clearable, @blur, @input 等)