工作中遇到一个场景:在商品列表页面展示时,往往可能需要展示不同的样式,比如单列,双列,三列甚至更多,有些时候页面元素展示的也不一样。
我们发现这些组件都是有共同点的,比如接口数据结构是相同的(当然如果接口数据结构不同也可以多加一层转换,总归需要展示的元素就那么几个),数据处理相同,比如价格处理,图片处理,标签处理,跳转,加购等等
开发中我发现有些同学是直接把老组件复制一下直接修改,只要用到类似组件的地方全都复制,导致整个项目在迭代的时候是在难以维护
好一点的会把dom合在一起,导致模板中一堆的if else逻辑,耦合性太重,也不利于维护
还有些同学可能会用到mixin,mixin的坏处这里就不再多说了
那么我们今天进入正题,看看用无渲染组件怎么处理
作用域插槽
在vue中slot是一个很重要的元素,对于我们提升代码质量来说至关重要,所以大家一定要掌握好
先简单回顾一下插槽的用法
// goosdList
<template>
<div class="view-content">
<goods>
<template v-slot="{compName, dealfatherMsg, fatherMsgFn}">
<div @click="fatherMsgFn(fatherMsg)">
{{ compName }}
<br>
{{ dealfatherMsg }}
</div>
</template>
<template v-slot:footer="{ footerMsg }">
<p> {{ footerMsg }}</p>
</template>
</goods>
</div>
</template>
<script>
import goods from './goods.vue'
export default {
components: {
goods
},
data() {
return {
fatherMsg: '我是父组件的信息'
}
}
}
</script>
<template>
<div class="view-content">
<slot :compName="compName" :dealfatherMsg="dealfatherMsg" :fatherMsgFn="fatherMsgFn"></slot>
<slot name="footer" :footerMsg="footerMsg"></slot>
</div>
</template>
<script>
export default {
data() {
return {
compName: '我是子组件',
footerMsg: '我是footerMsg',
dealfatherMsg: ''
}
},
methods: {
fatherMsgFn(fatherMsg) {
this.dealfatherMsg = fatherMsg + ', 我收到了'
}
},
}
</script>
可以简单的敲一下上面的小例子,展示了默认插槽,具名插槽和作用域插槽的用法。
你会发现作用域插槽中
- slot组件可以像对组件传递 props 那样,向一个插槽的出口上传递 attributes
- 另外slot使用者也可以通过暴露的方法向slot组件传递数据
所以当面试的时候问到数据传递时可以多说一种方式了
无渲染组件
有了作用域插槽的基础,我们来了解一下无渲染组件
无渲染组件是一个不需要渲染任何自己的HTML的组件。相反,它只管理状态和行为。它会暴露一个单独的作用域,让父组件或消费者完全控制应该渲染的内容。
无渲染组件可以在没有任何额外的元素情况之下精确的渲染你所传递的内容。简单的说就是下面这样
// parent.vue
<parent>
<template slot-scope="{}">
<h1>Hello world!</h1>
</template>
</parent>
表现和行为分离
这样有什么用呢,前面说到无状态组件只管理状态和行为,我们可以这么理解,把组件中所有的数据,处理方法,以及事件等等收集起来抽离出去
然后使用作用域插槽把父组件需要用到的信息暴露出去,那么js逻辑全都放在插槽中处理,父组件就只需要关心样式展示就行了,不同父组件的只需要修改自己的样式就可以了
使用案例
// child.vue
<script>
import { getRecommendList } from '@/api/home' //分页查询为你推荐
export default {
props: {
url: String
},
data() {
return {
skuList: []
}
},
created() {
this.getData()
},
methods: {
async getData() {
let params = {"orderCode":"","spuId":"7248","pageType":10,"pageNum":0,"pageSize":10,"subjectId":"116","recommendNum":""}
// 这里使用传进来的url,获取自己的数据
// 甚至可以传进来dataOpeFn处理把数据处理成自己想要的
let res = await getRecommendList(params)
this.skuList = res.data
},
toDetailFn(item) {
let path = 'spudetail',
sourceType = item.sourceType || item.goodsSource,
query = { id: item.id, storeId: item.storeId, sourceType: sourceType || 1 }
this.$router.push({ path, query })
}
},
render() {
return this.$scopedSlots.default({
skuList: this.skuList,
toDetailFn: this.toDetailFn
})
}
}
</script>
注意这里获取数据这块,这里可以使用传进来的url,获取自己的数据
slot中处理好数据暴露出来,不同组件修改自己的结构和样式就可以了
<template>
<div class="view-content">
<h1>两行布局</h1>
<child :url="url" v-slot="{skuList, toDetailFn}">
<div class="three-wraper">
<div class="three" v-for="item in skuList" :key="item.id" @click="toDetailFn(item)">
<img :src="item.url">
<p>{{ item.name }}</p>
<p class="price">{{ item.price }}元</p>
</div>
</div>
</child>
</div>
</template>
<script>
import child from './child.vue'
export default {
components: {
child
},
data() {
return {
url: 'your url'
}
},
}
</script>
总结
如果你满足下面这些条件,建议你使用无渲染组件这种方式:
- 你正在构建一个库,并且希望使用者能够更轻易地自定义组件的外观(样式效果)
- 你项目中有多个组件,其行为或功能非常相似,但布局不同
如果一个组件看起来和它使用的地方是一样的或者只需要一个组件,那么就完全没有必要分离,因为将所有东西都保存在一个组件中,会让你的事情变得更简单得多
参考文章:Vue 中的无渲染组件
原文链接:https://juejin.cn/post/7311881316052467746 作者:houlian