背景
有点高阶组件的感觉,把一个组件功能增强或修改组件的一些默认配置,同时外界能像原有组件那样使用新的组件。不过高阶组件是一个函数,而我们是直接写一个新的 Vue 组件,相当于是直接写这个函数的返回值了。
假设有一个 Input 组件,可以使用 v-model ,可以传 Props ,可以触发事件,可以传 Slots (包括 scopedSlots)。
但是我们在使用过程中发现,它的配置太自由了,但默认值不是我们想用的,每次使用都要配置一次,而且我们希望旁边能有个按钮,加个搜索功能。
于是我们新建一个 Vue 组件 SearchInput
Template
首先来实现新组件的 DOM 结构:
| 12
 3
 4
 5
 6
 
 | <template><div>
 <Input/>
 <button @click="handleSearch">搜索</button>
 </div>
 </template>
 
 | 
Props
然后接收 Input 组件所有的 Props ,甚至你还想加几个 Props 上去:
| 12
 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 上:
| 12
 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 以后直接
| 12
 
 | v-bind="$attrs"v-on="$listeners"
 
 | 
一般就可以实现 v-model 了,涉及原生的可能需要重写 input 事件监听器。
2.6.2 以前的版本有个 issue #8430 ,在外部使用 v-model 不会把 value prop 传入 $attrs 中,因此需要手动声明 value prop 兼容:
| 12
 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 :
| 12
 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 分别传入:
| 12
 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>
 
 | 
至此就完成了包裹。