我已經在 Vue 2 專案上作業了一段時間,在升級我們的 linting 要求后,我發現我們的prop許多子組件中都有突變錯誤。在我們的專案中,我們將一個單例物件作為 prop 傳遞給許多組件,并且最初直接從子組件更新物件。Vue 似乎建議使用v-bind.syncprops從子組件更新的功能(或使用等效的v-bindand v-on)。然而,這并不能解決陣列中prop嵌套組件的修改問題。
以使用prop突變的這個(偽)代碼為例:
注意:假設const sharedObject: { arrayElements: Array<{ isSelected: boolean }> } = ...
頁面.vue
<template>
...
<Component1 :input1="sharedObject" />
...
</template>
組件1.vue
<template>
...
<template v-for="elem in sharedObject.arrayElements">
<Component2 :input2="elem" />
</template>
...
</template>
組件2.vue
<template>
...
<q-btn @click="input2.isSelected = !input2.isSelected"></q-btn>
...
</template>
input2.isSelected從 Vue 2 中的嵌套組件更新屬性的正確方法是什么?我想到的所有方法都有缺陷。
有缺陷的方法
我相信我們希望將input2.isSelected已修改的冒泡Component2到 中Page.vue,但是,這似乎要么導致代碼混亂,要么產生一種不安的感覺,即我們只是在以迂回的方式抑制 linting 錯誤。
為了演示“亂碼”方法,首先要注意Page.vue不知道elemin的索引sharedObject.arrayElements。因此,我們將需要發射一個目的是Page.vue從Component1含有的狀態input2.isSelected為好的索引elem在sharedObject.arrayElements。這很快就會變得混亂。我們有的例子怎么樣:
組件1.vue
<template>
...
<template v-for="elem in sharedObject.arrayElements">
<template v-for="elem2 in elem.arrayElements">
<Component2 :input2="elem2" />
</template>
</template>
...
</template>
in this case, then we could need to pass up 2 indices! It doesn't seem like a sustainable solution to me.
The alternative that I thought of is a callback function (passed as a prop through the component hierarchy) that takes as input the element we want to update and an object that contains the properties we want to update (using Object.assign).
這讓我很不安,因為我不知道我們不能從子組件更新傳遞參考屬性的真正原因。對我來說,這似乎只是一種在Component2沒有 linter 注意到的情況下更新傳入的迂回方式。如果 props 傳遞給子組件時發生了一些神奇的修改,那么肯定將我收到的物件傳遞Component2給回呼函式并在父組件中修改它基本上只是更新子組件中的 prop組件,但更復雜。
在 Vue 2 中解決這個問題的正確方法是什么?
uj5u.com熱心網友回復:
關于 Vue 生態系統中這個長期存在的問題的現狀的很好的問題和分析。
是的,從子級修改“值型別”道具是一個問題,因為它會產生運行時問題(父級在重新渲染時覆寫更改),因此 Vue 在發生這種情況時會生成運行時錯誤......
從“代碼作業正常”POV 可以修改作為 prop 傳遞的物件的屬性。不幸的是,社區中有一些有影響力的人(以及許多盲目追隨他們的人)認為這是一種反模式。我不同意這一點,并多次提出我的論點(例如這里)。您很好地描述了原因-它只會產生不必要的復雜性/樣板代碼...
所以你正在處理的實際上只是一個 linting 規則(vue/no-mutating-props)。有一個正在進行的問題/討論提出了配置選項,該選項應該允許通過許多好的論據來緩解規則的嚴格性,但維護人員很少關注它(也可以在那里提高您的聲音)
現在你可以做的是:
- 禁用規則(遠非完美但幸運的是,由于 Vue 運行時錯誤,您可以在開發程序中捕獲真正不正確的情況就好了)
- 接受現實并使用變通方法
解決方法 - 使用全域狀態(像 Vuex 或 Pinia 這樣的存盤)
注意:Pinia 是首選,因為下一版本的 Vuex 將具有相同的 API
一般想法是將 放在sharedObject商店中并使用道具僅將子組件導航到正確的物件 - 在您的情況下,Component2它將通過道具接收索引并使用它從商店中檢索正確的元素。
Stores 非常適合共享全域狀態,但使用只是為了克服 linting 規則是不好的。也因此,組件與存盤耦合,因此可重用性受到影響并且測驗更加困難
解決方法 - 事件
是的,僅使用事件就可以創建混亂和大量樣板代碼(特別是如果您嵌套超過 2 個級別的組件),但有一些方法可以使事情變得更干凈。
例如,在您的情況下Component2不需要知道索引,因為您可以像這樣處理事件
// Component1
<template>
...
<template v-for="elem in sharedObject.arrayElements">
<template v-for="(elem2, index) in elem.arrayElements">
<Component2 :input2="elem2" @update="updateElement($event, index)" />
</template>
</template>
...
</template>
在您的情況下,Component2句柄僅更改單個布爾屬性,因此$event可以是簡單的布林值。如果內部有多個屬性要更改Component2,$event可以是一個物件,您可以使用物件傳播語法來“簡化” Component2(使用一個事件而不是多個事件 - 每個屬性一個)
// Component2
<template>
...
<input v-model="someText" type="text">
<q-btn @click="updateInput('isSelected', !input2.isSelected)"></q-btn>
...
</template>
<script>
export default {
props: ['input2'],
computed: {
someText: {
get() { return this.input2.someText },
set(newVal) { updateInput('someText', newVal) }
}
},
methods: {
updateInput(propName, newValue) {
const updated = { ...this.input2 } // make a copy of input2 object
updated[propName] = newValue // change selected property
this.$emit('update', updated) // send updated object to parent
}
}
}
</script>
嗯...我更喜歡禁用規則并設定一些明確的命名約定來指示組件負責更改它的輸入...
請注意,還有其他解決方法,例如使用this.$parent,inject\provide或事件總線,但這些方法非常糟糕
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/366332.html
標籤:javascript Vue.js Vuejs2 Vue 组件 类星体框架
