簡體   English   中英

我可以修改 Vue.js VNodes 嗎?

[英]Can I modify Vue.js VNodes?

我想通過data對象為子VNode分配一些屬性和類。 那行得通。 但是在我的 Vue.js 調查期間,我沒有看到使用這種模式,這就是為什么我認為修改子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>

以下是使用無狀態功能組件的示例:

 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. 不,有很多問題。

更新

我發現的另一個好方法是將子VNode包裝到另一個使用適當數據對象創建的VNode中,如下所示:

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

使用這種方法,我不必擔心修改子VNode的。

先感謝您。

這樣做有潛在的問題。 非常謹慎地使用它可能是一種有用的技術,如果沒有簡單的替代方法,我個人很樂意使用它。 但是,您處於未記錄的領域,如果出現問題,您可能必須通過逐步調試 Vue 內部來進行調試。 它不適合膽小的人。

首先,一些類似的東西被其他人使用的例子。

  1. 補丁key
    https://medium.com/dailyjs/patching-the-vue-js-virtual-dom-the-need-the-explanation-and-the-solution-ba18e4ae385b
  2. Vuetify 從 mixin 中修補 VNode 的示例:
    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 不會更改。

另一個按鈕強制外部組件重新渲染。 這會在插槽中重新生成 VNode。 然后將這些傳遞給孩子,DOM 將得到更新。

Vue 正在對什么可以和不可以導致 VNode 更改和相應優化做出一些假設。 在 Vue 3 中,這只會變得更糟(我的意思是更好),因為會有更多這些優化出現。 Evan You 做了一個非常有趣的關於 Vue 3 的演示,涵蓋了即將到來的各種優化,假設某些事情不能改變,它們都屬於 Vue 的類別。

有一些方法可以修復這個例子。 當組件執行更新時,VNode 將包含對 DOM 節點的引用,因此可以直接更新它。 這不是很好,但可以做到。

我自己的感覺是,只有修復了你正在做的補丁,你才真正安全,這樣更新就不是問題了。 添加一些屬性或 CSS 類應該可以工作,只要您以后不想更改它們。

還有一類問題需要克服。 調整 VNode 可能非常繁瑣。 問題中的例子暗示了這一點。 如果data丟失怎么辦? 如果缺少attrs怎么辦?

在問題的作用域插槽示例中, child組件的<div>上有class="bar" 這在parent中被吹走了。 也許這是故意的,也許不是,但是嘗試將所有不同的對象合並在一起是相當棘手的。 例如, class可以是字符串、對象或數組。 Vuetify 示例使用_b ,它是 Vue 內部bindObjectProps的別名,以避免必須覆蓋所有不同的情況本身。

除了不同的格式之外,還有不同的節點類型。 節點不一定代表組件或元素。 還有文本節點和注釋,其中注釋節點是v-if的結果,而不是模板中的實際注釋。

正確處理所有不同的邊緣情況非常困難。 再說一次,這些邊緣情況可能不會對您實際想到的用例造成任何實際問題。

最后一點,以上所有內容僅適用於修改 VNode。 從插槽包裝 VNode 或在render函數中在它們之間插入其他子節點是完全正常的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM