coding
coding copied to clipboard
Vue 技巧:无渲染组件
Vue 技巧:无渲染组件
最近,使用 Vue 开发组件时遇到了这样一个问题:开发的组件所能够自定义的 props
比较多,导致使用该组件时需要传入太多属性,数据、样式控制什么的属性都在一起了,看起来很不美观,像下面这样:
<some-component title="我是标题" :pos="[0, 0]" :size="[500, 400]" :radius="[0, 0.5]" :borderWidth="2" :borderColor="'#fff'" :data="testData" />
虽然看起来可能还好,但是实际使用时可以自定义的属性是不止这些的,这样使用起来就很不美观了。于是就有了这样一个想法:定义一个类似于 React
中常写的 theme
组件,将一些非数据相关的 props
定义到 theme
组件上,theme
组件再自动将 props
透传给其他组件使用即可。theme
组件使用起来像这样:
<theme :pos="[0, 0]" :size="[500, 400]" :radius="[0, 0.5]" :borderWidth="2" :borderColor="'#fff'" >
<some-component title="我是标题":data="testData" />
</thmem>
开发 theme
组件
在 React
中,开发这样一个高阶组件 theme
是很简单的。但是在 vue
中如何开发 theme
组件以达到上面设想的使用效果?通过翻阅 Vue
的文档,发现借助 $slots
和 render
函数可以做到。
export default {
name: 'theme',
render(h) {
const theme = this.$attrs // 通过 $attrs 可以拿到使用该组件时定义的 props,而无需声明有哪些 props
const merge = vNode => {
if (!vNode.tag) {
return
}
if (vNode.componentOptions) {
let props = vNode.componentOptions.propsData
props = Object.assign({}, theme, props)
vNode.componentOptions.propsData = props
} else {
if (!vNode.data) {
return
}
let attrs = vNode.data.attrs || {}
attrs = Object.assign({}, theme, attrs)
vNode.data.attrs = attrs
}
}
this.$slots.default.map(vNode => merge(vNode))
Object.keys(this.$attrs).forEach(key => {
this.$attrs[key] = null
})
return this.$slots.default[0] // 直接返回,无需额外渲染
}
}
如此便达到了这样的使用效果:
<theme :pos="[0, 0]" :size="[500, 400]" :radius="[0, 0.5]" :borderWidth="2" :borderColor="'#fff'" >
<some-component title="我是标题":data="testData" />
</thmem>
很显然 theme
组件是没有渲染的,它所做的也只不过是透传 props 给其它组件而已,称之为 无渲染组件
。
slot-scope
在 Vue文档 中提到了 slot-scope
可以使用作用域插槽变得更干净。那么结合 theme
组件的经验,可以写出这样一款 axios
组件。
Vue.component('s-axios', {
props: ['url'],
data() {
return {
loading: true,
response: null
}
},
created() {
axios.get(this.url)
.then(response => {
this.loading = false
this.response = response
})
},
render() {
return this.$scopedSlots.default({
loading: this.loading,
response: this.response
})
}
})
使用起来也很方便:
<div id="app">
<s-axios url="https://api.github.com/orgs/reactjs/repos">
<div slot-scope="{ loading, response }">
<div v-if="loading">loading</div>
<div v-else>响应数据为:${{ response.data }}</div>
</div>
</s-axios>
</div>
可以点击查看在线Demo。
总结
通过 $slots
、$scopedSlots
结合 render
可以创造很多好玩的组件,比如本篇文章中说到的 无渲染组件 ,关键就在于使用者怎么想。
这个就是类似React里的render props吧
@riyueweiyi 是的【哈哈】
传多个参数用v-bind="data"不就可以了吗?
@kaysonli 可以,还是看个人开发时想怎么用。