Vue 2 Aggiornare le Proprietà di un Oggetto in un Array Nidificate Componente

0

Domanda

Ho lavorato su una Vue 2 progetto per un po', e dopo l'aggiornamento il nostro linting requisiti ho scoperto che abbiamo avuto prop mutazione errori in molti dei nostri componenti secondari. Nel nostro progetto, si passa un oggetto singleton come un puntello per molti componenti e sono stati originariamente aggiornare l'oggetto direttamente dai componenti secondari. Vue sembra suggerire utilizzando il v-bind.sync funzionalità per l'aggiornamento props da bambino i componenti (o equivalente v-bind e v-on). Questo, tuttavia, non risolve il problema di prop modifica componenti nidificati in un array.

Prendere questo (pseudo)codice di esempio che utilizza prop mutazione:

Nota: Si Supponga const sharedObject: { arrayElements: Array<{ isSelected: boolean }> } = ...

Pagina.vue

<template>
  ...
  <Component1 :input1="sharedObject" />
  ...
</template>

Component1.vue

<template>
  ...
  <template v-for="elem in sharedObject.arrayElements">
    <Component2 :input2="elem" />
  </template>
  ...
</template>

Component2.vue

<template>
  ...
  <q-btn @click="input2.isSelected = !input2.isSelected"></q-btn>
  ...
</template>

Qual è il modo corretto di aggiornamento di un immobile come input2.isSelected da componenti nidificati in Vue 2? Tutti gli approcci ho pensato sono viziati.

Imperfetto Approcci

Io credo che ci vorrebbe bolle che input2.isSelected è stato modificato in Component2 per Page.vuetuttavia, questo sembra portare a codice disordinato o una sensazione di disagio che siamo solo a reprimere linting errori in una rotonda modo.


Per dimostrare il "codice disordinato", la prima nota che Page.vue non si conosce l'indice di elem in sharedObject.arrayElements. Pertanto, si avrebbe bisogno di emettere un oggetto Page.vue da Component1 che contiene lo stato di input2.isSelected e dell'indice di elem in sharedObject.arrayElements. Questo si ottiene disordinato rapidamente. Che cosa circa l'esempio in cui abbiamo:

Component1.vue

<template>
  ...
  <template v-for="elem in sharedObject.arrayElements">
    <template v-for="elem2 in elem.arrayElements">
       <Component2 :input2="elem2" />
    </template>
  </template>
  ...
</template>

in questo caso, quindi, abbiamo bisogno di passare i 2 indici! Non sembra una soluzione sostenibile per me.


L'alternativa che ho pensato è una funzione di callback (passato come un sostegno attraverso la gerarchia dei componenti) che prende in input l'elemento che si desidera aggiornare e un oggetto che contiene le proprietà che si desidera aggiornare (utilizzando Object.assign).

Questo mi rende molto a disagio in quanto non so il vero motivo per cui non siamo in grado di aggiornare un passaggio per riferimento prop da un bambino di componente. A me sembra solo una rotonda modo di aggiornare in passato da Component2 senza linter se ne accorga. Se c'è qualcosa di magico, di modifica che succede puntelli quando sono passato al bambino di componenti, quindi sicuramente passando l'oggetto che ho ricevuto in Component2 la funzione di callback e modificando nel componente principale sarebbe fondamentalmente essere solo l'aggiornamento del pilone nel bambino componente, ma più complicato.

Qual è il corretto modo di affrontare questo problema in Vue 2?

1

Migliore risposta

3

Domanda molto buona e l'analisi dello stato attuale di questo annoso problema in Vue ecosistema.

Sì, la modifica del valore "tipo" di scena il bambino è un problema in quanto crea problemi di runtime (genitore sovrascrivere le modifiche quando ri-renderizzato) e quindi Vue genera un errore di runtime quando questo accade...

Modifica di una proprietà dell'oggetto passato come l'elica è OK dal "codice funziona bene" POV. Purtroppo ci sono alcune persone influenti nella comunità con parere (e di molti di coloro che seguono ciecamente di loro) che questo è un anti-pattern. Io non sono d'accordo con che e cresciuto i miei argomenti molte volte (ad esempio qui). Hai descritto i motivi che ben si crea solo inutili complessità/codice standard...

Così che cosa avete a che fare con è in realtà solo un linting regola (vue/no-mutazione-oggetti di scena). C'è in corso un problema/discussione che propone l'opzione di configurazione che dovrebbe consentire di facilitare il rigore della regola, con molti buoni argomenti, ma si ottiene poca attenzione manutentori (sentitevi liberi di alzare la voce anche lì)

Per ora ciò che si può fare è:

  1. Disabilitare la regola (lungi dall'essere perfetto, ma per fortuna grazie a una Vue errore di runtime si può prendere il vero non corretto dei casi durante lo sviluppo benissimo)
  2. Accettare la realtà e l'utilizzo di soluzioni alternative

Soluzione: usare globale stato (negozio, come Vuex o Pinia)

Nota: Pinia è preferito, come la prossima versione di Vuex avranno le stesse API

Idea generale è quella di posizionare il sharedObject nel conservare e utilizzare oggetti di scena solo per passare il bambino di componenti per l'oggetto giusto nel vostro caso, l' Component2 verrà visualizzato un indice di elica e recuperare il giusto elemento per il negozio utilizza.

I negozi sono grandi per la condivisione globale stato, ma l'utilizzo è solo per superare il linting regola è male. Inoltre, come risultato, i componenti sono accoppiati al negozio, quindi, sia la riutilizzabilità soffre e la prova è più difficile

Soluzione - eventi

Sì, è possibile creare confusione e un sacco di codice standard, utilizzando solo gli eventi (soprattutto se si annidano i componenti più di 2 livelli), ma ci sono modi per rendere le cose più pulito.

Per esempio nel tuo caso Component2 non ha bisogno di conoscere l'indice, come si può gestire l'evento come questo

// 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>

Nel tuo caso, il Component2 gestisce solo il cambiamento di singola proprietà booleana così $event può essere semplice booleano. Se ci sono più di una proprietà da modificare all'interno Component2, $event può essere un oggetto e si può utilizzare l'oggetto di diffusione sintassi per "semplificare" la Component2 (utilizzando un evento, invece di più - uno per ogni proprietà)

// 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>

Beh...io preferisco solo per disabilitare la regola e definire chiaramente le convenzioni di denominazione per indicare che il componente è responsabile per la modifica di ingresso...

Nota che ci sono altre soluzioni come l'utilizzo di this.$parent, inject\provide o event bus, ma quelli sono davvero male

2021-11-24 09:42:19

In altre lingue

Questa pagina è in altre lingue

Русский
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Türk
..................................................................................................................
Česk
..................................................................................................................
Português
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................