Sunday, 1 September 2019

Can I modify Vue.js VNodes?

I want to assign some attributes and classes to the children VNode through data object? Everything works perfectly. But during my Vue.js investigation, I have not seen such pattern in use, that's why I don't think it's good idea to modify children VNode's.

But that approach sometimes comes in handy – for example I want to assign to all the buttons in default slot the aria-label attribute.

See example below, using default stateful components:

Vue.component('child', {
  template: '<div>My role is </div>',
})

Vue.component('parent', {
  render(h) {
    const {
      default: defaultSlot
    } = this.$slots

    if (defaultSlot.length) {
      defaultSlot.forEach((child, index) => {
        if (!child.data) child.data = {}
        if (!child.data.attrs) child.data.attrs = {}

        const {
          data
        } = child

        data.attrs.role = 'button'
        data.class = 'bar'
        data.style = `color: #` + index + index + index
      })
    }

    return h(
      'div', {
        class: 'parent',
      },
      defaultSlot,
    )
  },
})

new Vue({
  el: '#app',
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <parent>
    <child></child>
    <child></child>
    <child></child>
    <child></child>
    <child></child>
  </parent>
</div>

And here is examples using stateless functional components:

Vue.component('child', {
  functional: true,
  render(h, {
    children
  }) {
    return h('div', {
      class: 'bar'
    }, children)
  },
})

Vue.component('parent', {
  functional: true,
  render(h, {
    children,
    scopedSlots
  }) {
    const defaultScopedSlot = scopedSlots.default({
      foo: 'bar'
    })

    if (defaultScopedSlot.length) {
      defaultScopedSlot.forEach((child, index) => {
        child.data = {
          style: `color: #` + index + index + index
        }
        
        child.data.attrs = {
          role: 'wahtever'
        }
      })
    }
    return h(
      'div', {
        class: 'parent',
      },
      defaultScopedSlot,
    )
  },
})

new Vue({
  el: '#app',
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
  <parent>
    <template v-slot:default="{ foo }">
    <child></child>
    <child></child>
    <child></child>
  </template>
  </parent>
</div>

I am waiting for the following answers:

  1. Yes, you can use it, there are no potential problems with this approach.

  2. Yes, but these problem(s) can happen.

  3. No, there are a lot of problem(s).

UPDATE:

That another good approach I have found it's to wrap child VNode into the another created VNode with appropriate data object, like this:

const wrappedChildren = children.map(child => {
  return h("div", { class: "foo" }, [child]);
});

Using this approach I have no fear modifying children VNode's.

Thank you in advance.



from Can I modify Vue.js VNodes?

No comments:

Post a Comment