背景
有点高阶组件的感觉,把一个组件功能增强或修改组件的一些默认配置,同时外界能像原有组件那样使用新的组件。不过高阶组件是一个函数,而我们是直接写一个新的 Vue 组件,相当于是直接写这个函数的返回值了。
假设有一个 Input 组件,可以使用 v-model ,可以传 Props ,可以触发事件,可以传 Slots (包括 scopedSlots)。
但是我们在使用过程中发现,它的配置太自由了,但默认值不是我们想用的,每次使用都要配置一次,而且我们希望旁边能有个按钮,加个搜索功能。
于是我们新建一个 Vue 组件 SearchInput
Template
首先来实现新组件的 DOM 结构:
1 2 3 4 5 6
| <template> <div> <Input/> <button @click="handleSearch">搜索</button> </div> </template>
|
Props
然后接收 Input 组件所有的 Props ,甚至你还想加几个 Props 上去:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <template> <div> <Input v-bind="$attrs" /> <button v-if="canSearch" @click="handleSearch" >搜索</button> </div> </template>
<script> export default { name: 'SearchInput', props: { canSearch: { type: Boolean, default: true, }, }, } </script>
|
Events
使用 $listeners 把监听器都挂 Input 上:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <template> <div> <Input v-bind="$attrs" v-on="$listeners" /> <button v-if="canSearch" @click="handleSearch" >搜索</button> </div> </template>
<script> export default { name: 'SearchInput', props: { canSearch: { type: Boolean, default: true, }, }, methods: { handleSearch () { this.$emit('search') }, }, } </script>
|
也可以在 v-on
上绑定一个 computed ,对 $listeners
做手脚。
v-model
Vue 版本 2.6.2 以后直接
1 2
| v-bind="$attrs" v-on="$listeners"
|
一般就可以实现 v-model
了,涉及原生的可能需要重写 input 事件监听器。
2.6.2 以前的版本有个 issue #8430 ,在外部使用 v-model 不会把 value prop 传入 $attrs 中,因此需要手动声明 value prop 兼容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <template> <div> <Input :value="value" v-bind="$attrs" v-on="$listeners" /> <button v-if="canSearch" @click="handleSearch" >搜索</button> </div> </template>
<script> export default { name: 'SearchInput', props: { value: {},
canSearch: { type: Boolean, default: true, }, }, methods: { handleSearch () { this.$emit('search') }, }, } </script>
|
Slots
在 Vue 2.6 以后推出了新语法 v-slot
,因此我们可以这样传入所有 slots :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| <template> <div> <Input :value="value" v-bind="$attrs" v-on="$listeners" > <template v-for="(_, slotName) in $scopedSlots" v-slot:[slotName]="slotProps" > <slot :name="slotName" v-bind="slotProps" ></slot> </template> </Input> <button v-if="canSearch" @click="handleSearch" >搜索</button> </div> </template>
<script> export default { name: 'SearchInput', props: { value: {},
canSearch: { type: Boolean, default: true, }, }, methods: { handleSearch () { this.$emit('search') }, }, } </script>
|
(在此吐槽一句,新语法就意味着新坑)
2.6 以下的,老样子,把 $slots, $scopedSlots 分别传入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| <template> <div> <Input :value="value" v-bind="$attrs" v-on="$listeners" > <slot v-for="(_, slotName) in $slots" :name="slotName" :slot="slotName" ></slot> <template v-for="(_, slotName) in $scopedSlots" :slot="slotName" slot-scope="slotProps" > <slot :name="slotName" v-bind="slotProps" ></slot> </template> </Input> <button v-if="canSearch" @click="handleSearch" >搜索</button> </div> </template>
<script> export default { name: 'SearchInput', props: { value: {},
canSearch: { type: Boolean, default: true, }, }, methods: { handleSearch () { this.$emit('search') }, }, } </script>
|
至此就完成了包裹。