Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue 技巧:无渲染组件 #22

Open
fantasticit opened this issue Dec 1, 2018 · 4 comments
Open

Vue 技巧:无渲染组件 #22

fantasticit opened this issue Dec 1, 2018 · 4 comments
Labels

Comments

@fantasticit
Copy link
Owner

fantasticit commented Dec 1, 2018

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 的文档,发现借助 $slotsrender 函数可以做到。

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

@riyueweiyi
Copy link

这个就是类似React里的render props吧

@fantasticit
Copy link
Owner Author

@riyueweiyi 是的【哈哈】

@kaysonli
Copy link

kaysonli commented Jan 7, 2019

传多个参数用v-bind="data"不就可以了吗?

@fantasticit
Copy link
Owner Author

@kaysonli 可以,还是看个人开发时想怎么用。

Repository owner deleted a comment from yyman001 Jan 14, 2019
Repository owner deleted a comment from yyman001 Jan 14, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants