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
可以创造很多好玩的组件,比如本篇文章中说到的 无渲染组件 ,关键就在于使用者怎么想。