Skip to content

Vue 技巧:无渲染组件 #22

Description

@fantasticit

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions