我想给VNodedata的子对象分配一些属性和类.这很管用.但在我的Vue期间.js调查,我还没有看到这样的模式在使用,这就是为什么我不认为这是一个好主意,修改 children VNode的.

但这种方法有时会派上用场——例如,我想为默认插槽中的所有按钮指定aria-label属性.

请参见下面的示例,使用默认有状态组件:

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

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

    if (defaultSlot) {
      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>

下面是使用stateless个功能组件的示例:

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

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

    if (defaultScopedSlot) {
      defaultScopedSlot.forEach((child, index) => {
        child.data = {
          style: `color: #` + index + index + index
        }

        child.data.attrs = {
          role: 'whatever'
        }
      })
    }
    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>{{ foo }}</child>
      <child>{{ foo }}</child>
      <child>{{ foo }}</child>
    </template>
  </parent>
</div>

我在等待以下答案:

  1. 是的,你可以使用它,这种方法没有潜在的问题.

  2. 是的,但这些问题可能会发生.

  3. 不,有很多问题.

UPDATE:

我发现的另一个很好的方法是将child VNode包装到另一个创建的VNode中,并使用适当的数据对象,如下所示:

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

使用这种方法,我不怕修改 children VNode岁.

提前谢谢你.

推荐答案

这样做有潜在的问题.使用非常少,这可能是一个有用的技术,我个人很乐意使用它,如果没有简单的替代品可用.然而,您处于未记录的区域,如果出现问题,您可能必须通过逐步执行Vue内部 struct 进行调试.它不适合胆小的人.

首先,一些类似的例子被其他人使用.

  1. Patching key:
    https://medium.com/dailyjs/patching-the-vue-js-virtual-dom-the-need-the-explanation-and-the-solution-ba18e4ae385b
  2. An example where Vuetify patches a VNode from a mixin:
    https://github.com/vuetifyjs/vuetify/blob/5329514763e7fab11994c4303aa601346e17104c/packages/vuetify/src/components/VImg/VImg.ts#L219
  3. Vuetify从作用域插槽修补VNode的示例:https://github.com/vuetifyjs/vuetify/blob/7f7391d76dc44f7f7d64f30ad7e0e429c85597c8/packages/vuetify/src/components/VItemGroup/VItem.ts#L58

我认为只有第三个例子可以与这个问题中的补丁相比.其中的一个关键特性是,它使用了一个作用域插槽而不是普通插槽,因此VNode是在同一个render函数中创建的.

使用普通插槽会变得更复杂.问题在于,插槽的VNode是在父级的render函数中创建的.如果子函数的render函数多次运行,它只会不断为插槽传递相同的Vnode.修改这些VNode不一定会达到预期效果,因为扩散算法只会看到相同的VNode,不会执行任何DOM更新.

下面是一个例子来说明:

const MyRenderComponent = {
  data () {
    return {
      blueChildren: true
    }
  },

  render (h) {
    // Add a button before the slot children
    const children = [h('button', {
      on: {
        click: () => {
          this.blueChildren = !this.blueChildren
        }
      }
    }, 'Blue children: ' + this.blueChildren)]

    const slotContent = this.$slots.default

    for (const child of slotContent) {
      if (child.data && child.data.class) {
        // Add/remove the CSS class 'blue'
        child.data.class.blue = this.blueChildren
        
        // Log it out to confirm this really is happening
        console.log(child.data.class)
      }
      
      children.push(child)
    }

    return h('div', null, children)
  }
}

new Vue({
  el: '#app',

  components: {
    MyRenderComponent
  },

  data () {
    return {
      count: 0
    }
  }
})
.red {
  border: 1px solid red;
  margin: 10px;
  padding: 5px;
}

.blue {
  background: #009;
  color: white;
}
<script src="https://unpkg.com/vue@2.6.10/dist/vue.js"></script>
<div id="app">
  <my-render-component>
    <div :class="{red: true}">This is a slot content</div>
  </my-render-component>
  <button @click="count++">
    Update outer: {{ count }}
  </button>
</div>

有两个按钮.第一个按钮切换名为blueChildrendata属性.它用于决定是否向子类添加CSS类.更改blueChildren的值将成功触发子组件的重新渲染,VNode确实会得到更新,但DOM不变.

另一个按钮强制外部组件重新渲染.在插槽中重新生成.然后将这些信息传递给子对象,DOM将得到更新.

Vue正在对哪些因素可以和哪些因素不能导致VNode发生变化做出一些假设,并进行相应的优化.在Vue 3中,这只会变得更糟(我的意思是更好),因为有更多这样的优化.关于Vue 3有a very interesting presentation Evan You gave种,涵盖了即将到来的各种优化,它们都属于这类Vue,假设某些事情无法改变.

有很多方法可以修复这个例子.当组件执行更新时,VNode将包含对DOM node 的引用,因此可以直接更新它.这不太好,但可以做到.

我自己的感觉是,只有当你正在做的补丁修复了,更新没有问题时,你才是真正安全的.添加一些属性或CSS类应该可以,只要你以后不想更改它们.

还有另一类问题需要克服.调整Vnode非常复杂.问题中的例子暗示了这一点.如果data人失踪怎么办?如果attrs不见了怎么办?

在问题中的作用域插槽示例中,child组件的<div>上有class="bar"个.这在parent中被吹走了.也许这是有意的,也许不是,但试图将所有不同的对象合并在一起是相当棘手的.例如,class可以是字符串、对象或array.Vuetify示例使用_b,这是Vue内部bindObjectProps的别名,以避免覆盖所有不同的情况.

除了不同的格式之外,还有不同的 node 类型. node 不一定代表组件或元素.还有文本 node 和注释,其中注释 node 是模板中v-if条注释的结果,而不是实际注释.

正确处理所有不同的边缘情况是相当困难的.再说一次,这些边缘 case 可能都不会对您实际考虑的用例造成任何真正的问题.

最后,以上所有内容仅适用于修改VNode.在render函数中,从插槽中包装VNode或在它们之间插入其他子 node 是完全正常的.

Vue.js相关问答推荐

Vue 3 需要 main.js 中的文件

作为props 传递的原始 ref 的 Vue 3 react 性

在 v-for 中定义变量有什么问题吗?

Vue Datepicker 不会在渲染时显示默认日期

为什么我在 Vue npm run serve 的 localhost 中得到无法获取

如何容器化 Vue.js 应用程序?

从数据库中删除后,Vue.js $remove 不删除元素

vue js 组件中的 this.$route 只返回 undefined

带有外部配置文件的 Vue js

如何在异步功能上使用 debounce?

在textbox文本框 VueJS 2 中键入时搜索列表

ionic - `slot` 属性已被弃用 - eslint-plugin-vue

使用 nuxt,如何将路由名称放在页面标题中?

如何在 Vue.js 应用程序中正确下载 Excel 文件?

如何在 VueJS 中下载本地存储的文件

在 VueJS 中使用 Axios - 这个未定义

脚本npm run dev和npm run watch是做什么用的?

vue 未在实例上定义,但在渲染期间引用

Vuejs 不会在 HTML 表格元素中呈现组件

VueJS 2 + ASP.NET MVC 5