La bonne façon de réaliser le découplage des composants
P粉476475551
P粉476475551 2023-09-14 10:57:11
0
1
560

Je m'occupe de la communication entre deux composants (utilisant Vue 2), dont l'un est un bouton, qui peut avoir des états comme initial, chargement et achèvement (succès ou échec), pour chaque état du bouton, je peux afficher un état différent texte, différentes icônes (chargement : icône de rotation, réussite : coche, erreur de complétion : x), j'ai aussi un formulaire qui utilisera un composant bouton. Je ne sais pas comment modifier l'état du bouton en fonction de l'état actuel de la soumission du formulaire. Veuillez consulter le code ci-dessous.

Mon composant bouton :

<template>
 <button
    class="ui-button"
    @click="clicked"
    :data-status-type="status_type"
    :disabled="is_disabled"
    :type="type"
  >
    <i :class="icon" v-if="is_disabled || concluded"></i>
    {{ title }}
  </button>         
</template>

<script>
export default {
  props: {
    title: {
      type: String,
    },
    type: {
      default: "button",
      type: String,
    },
  },
  data() {
    return {
      concluded: false,
      icon: "fa fa-spin ",
      is_disabled: false,
      status_type: "success",
    };
  },
  methods: {
    clicked() {
      if (!this.is_disabled) {
        this.$emit(
          "clicked",
          () => {
            this.is_disabled = true;
            this.icon = "fa fa-spin fas fa-spinner";
          },
          (succeeded) => {
            this.is_disabled = false;
            this.concluded = true;
            this.icon = succeeded ? "fas fa-check" : "fas fa-xmark";
            this.status_type = succeeded ? "success" : "error";
            setTimeout(() => {
              this.concluded = false;
              this.icon = "";
              this.status_type = "";
            }, 1500);
          }
        );
      }
    },
  },
};
</script>

Mon composant de formulaire :

<template>
  <div>
    <ThePages :parents="accompaniments">
       <!--  ... some reactive stuff  -->
      <template #extra_button>
        <TheButton @clicked="sendItemToCart" :title="button_text" :disabled="button_disabled" />
      </template>
    </ThePages>
  </div>
</template>

<script>
import axios from 'axios'
import FormatHelper from '../helpers/FormatHelper'
import SwalHelper from '../helpers/SwalHelper'
import TheButton from './TheButton.vue'
import ThePages from './ThePages.vue'
import TheQuantityPicker from './TheQuantityPicker.vue'

export default {
  props: ['product'],
  components: {
    TheButton,
    ThePages,
    TheQuantityPicker,
  },
  data() {
    return {
      accompaniments: this.product.accompaniment_categories,
      button_text: '',
      button_disabled: false,
      format_helper: FormatHelper.toBRCurrency,
      observation: '',
      quantity: 1,
      success: false,
    }
  },
  created() {
    this.addQuantityPropToAccompaniments()
    this.availability()
  },
  methods: {
    // ... some other methods
    async sendItemToCart(startLoading, concludedSuccessfully) {
      startLoading()  // This will change the button state
      this.button_text = 'Adicionando...'
      await axios
        .post(route('cart.add'), {
          accompaniments: this.buildAccompanimentsArray(),
          id: this.product.id,
          quantity: this.quantity,
          observation: this.observation,
        })
        .then(() => {
          concludedSuccessfully(true)  // This will change the button state
          this.button_text = 'Adicionado'
          SwalHelper.productAddedSuccessfully()
        })
        .catch((error) => {
          concludedSuccessfully(false)  // This will change the button state
          if (
            error?.response?.data?.message ==
            'Este produto atingiu a quantidade máxima para este pedido.'
          ) {
            SwalHelper.genericError(error?.response?.data?.message)
          } else {
            SwalHelper.genericError()
          }
          this.button_text = 'Adicionar ao carrinho'
        })
    },
  },
}
</script>

Dans le code ci-dessus, vous pouvez voir comment je change l'état du bouton en fonction de l'état du formulaire : Mon bouton émet deux fonctions lorsqu'on clique dessus (startLoading, IncludedSuccessfully), et j'utilise ensuite ces deux fonctions dans sendItemToCart.

Cela semble un peu trop coupler les deux composants, car je dois passer ces fonctions en paramètres aux méthodes du composant parent. De plus, j'ai une autre idée sur la façon de procéder, qui consiste à donner à chaque bouton une référence, puis à appeler sa méthode en utilisant la référence dans le composant parent. L'idée ressemble un peu à "la composition au lieu de l'héritage" dans la programmation orientée objet, où je demande simplement à l'objet/composant de faire quelque chose, mais dans ce cas, sans prendre la fonction comme paramètre.

Eh bien, les deux cas ci-dessus semblent meilleurs que de créer une variable pour chaque bouton que je pourrais avoir, mais ils semblent pouvoir être améliorés. Ce que je recherche donc, c'est : Comment puis-je mieux découpler mes composants ?

P粉476475551
P粉476475551

répondre à tous(1)
P粉023650014

Si nous parlons de Vue 3 (vous n'avez pas précisé la version de Vue, donc pas sûr que ce soit Vue 2), vous recherchez probablement provide/inject :

https://vuejs.org/guide/components/provide-inject.html

Donc, si vous avez un composant parent et un ensemble de composants enfants qui ne peuvent apparaître qu'en tant que descendants du composant parent (comme des formulaires et des zones de saisie), vous pouvez provideindiquer le formulaire :

OP a commenté que les boutons peuvent également apparaître ailleurs, nous devrions donc utiliser à la fois props et provide. En l'absence de formulaire, nous pouvons utiliser props comme valeur injectée par défaut.

Dans le composant formulaire :

<script setup>

import {reactive, provide} from 'vue';

const form = reactive({button_disabled: false, button_text: '', sendItemToCart});
provide('form', form);

function sendItemToCart(){
  // your logic here
}

</script>

<template>
  <div>
    <ThePages :parents="accompaniments">
       <!--  ... some reactive stuff  -->
      <template #extra_button>
        <TheButton />
      </template>
    </ThePages>
  </div>
</template>

Dans le composant bouton :

<script setup>

import {inject} from 'vue';

const props = defineProps('button_disabled button_text'.split(' '));
const form = inject('form', props); // use either provide of props

</setup>

<template>
 <button
    class="ui-button"
    @click="() => form.sendItemToCart ? form.sendItemToCart() : $emit('clicked')"
    :data-status-type="status_type"
    :disabled="form.button_disabled"
    :type="type"
  >
    <i :class="icon" v-if="form.button_disabled || concluded"></i>
    {{ form.button_text }}
  </button>         
</template>

Ajustez le code à l'API Options.

Mise à jour avec Vue 2

OP a corrigé la réponse en utilisant Vue 2. Alors...

Heureusement, Vue 2 le prend également en chargeprovide/inject ! Le seul problème était de savoir comment rendre la fourniture réactive, ce qui, je suppose, est résolu ici :

Comment rendre l'API Provide/Inject de Vue 2 réactive ?

Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal