Meilleure pratique : comment attacher des composants au DOM dans Vue 3
P粉362071992
P粉362071992 2023-08-24 19:51:03
0
2
586
<p>Je souhaite créer dynamiquement un composant dans mon application Vue 3, à l'intérieur d'un composant de fichier unique (SFC), et l'ajouter au DOM. J'utilise un composant de style <code><script setup></code> </p> <p>Cela semble inutilement difficile. </p> <p>Voici à peu près ce que je veux faire :</p> <ol> <li>Obtenir des données. A été complété. ≪/li> <li>Créez une instance du composant Vue : Foo.vue. ≪/li> <li>Transmettez-lui les données en tant que propriétés. ≪/li> <li>Ajoutez-le là où je le souhaite. ≪/li> </ol> <p>Le problème est que je ne peux pas utiliser <component :is="Foo:> dans le modèle car je ne sais pas où il se trouvera longtemps après le rendu du modèle.</p> <p>Existe-t-il des bonnes pratiques ? Y a-t-il une personne aimable qui peut fournir un exemple simple, je lui serais très reconnaissant. </p> <p>Parfois, je n'arrive pas à comprendre la documentation de Vue la moitié du temps. Désolé, je déteste le dire, mais pour un débutant sur Vue, ils sont assez obscurs et me font me sentir stupide. </p> <p>Voici un faux code pour ce que j'essaie de faire : </p> <pre class="brush:php;toolbar:false;">importer Foo depuis "../components/Foo.vue" fonction makeAFoo(p, données){ // Instancier mon Foo.vue (je ne sais pas comment faire cela en ligne) et lui transmettre les données requises let foo = new Foo(data); // Si seulement c'était aussi simple, non ? //Ajoutez-le à p (qui est un élément HTML) p.appendChild(foo) }</pré> <p><br /></p>
P粉362071992
P粉362071992

répondre à tous(2)
P粉004287665

Un moyen plus simple consiste à utiliser v-if ou v-for.

Au lieu de gérer le composant directement, gérons l'état du composant et laissons la magie de la réactivité de Vue opérer

Ceci est un exemple, ajoutez dynamiquement un composant (Toast), exploitez simplement l'état du composant

Fichier Toast.vue : Le v-for ici est réactif, chaque fois qu'une nouvelle erreur est ajoutée à l'objet erreurs, elle sera rendue

<script setup lang="ts">
import { watch } from 'vue';
import { ref, onUpdated } from 'vue';
import { Toast } from 'bootstrap';

const props = defineProps({
  errors: { type: Array, default: () => [] },
});

onUpdated(() => {
  const hiddenToasts = props.errors.filter((obj) => {
    return obj.show != true;
  });
  hiddenToasts.forEach(function (error) {
    var errorToast = document.getElementById(error.id);
    var toast = new Toast(errorToast);
    toast.show();
    error.show = true;
    errorToast.addEventListener('hidden.bs.toast', function () {
      const indexOfObject = props.errors.findIndex((item) => {
        return item.id === error.id;
      });
      if (indexOfObject !== -1) {
        props.errors.splice(indexOfObject, 1);
      }
    });
  });
});
</script>
<script lang="ts">
const TOASTS_MAX = 5;
export function push(array: Array, data): Array {
  if (array.length == TOASTS_MAX) {
    array.shift();
    array.push(data);
  } else {
    array.push(data);
  }
}
</script>

<template>
  <div
    ref="container"
    class="position-fixed bottom-0 end-0 p-3"
    style="z-index: 11"
  >
    <div
      v-for="item in errors"
      v-bind:id="item.id"
      class="toast fade opacity-75 bg-danger"
      role="alert"
      aria-live="assertive"
      aria-atomic="true"
      data-bs-delay="15000"
    >
      <div class="toast-header bg-danger">
        <strong class="me-auto text-white">Error</strong>
        <button
          type="button"
          class="btn-close"
          data-bs-dismiss="toast"
          aria-label="Close"
        ></button>
      </div>
      <div class="toast-body text-white error-body">{{ item.msg }}</div>
    </div>
  </div>
</template>

ErrorTrigger.vue : chaque fois qu'un événement de clic se produit, nous transmettons une erreur à l'objet erreurs

<script setup lang="ts">
import { ref, reactive } from 'vue';
import toast from './Toast.vue';
import { push } from './Toast.vue';

const count = ref(0);
const state = reactive({ errors: [] });

function pushError(id: int) {
  push(state.errors, { id: id, msg: 'Error message ' + id });
}
</script>

<template>
  <toast :errors="state.errors"></toast>

  <button type="button" @click="pushError('toast' + count++)">
    Error trigger: {{ count }}
  </button>
</template>

Exemple complet : https://stackblitz.com/edit/vitejs-vite-mcjgkl

P粉598140294

Option 1 : Utiliser createVNode(component, props)render(vnode, container)

Créer : Utilisez createVNode()创建一个带有props的组件定义的VNode(例如,从*.vue导入的SFC),可以将其传递给render() pour effectuer le rendu sur l'élément conteneur donné.

Destroy : Appelez le render(null, container)会销毁附加到容器的VNode。当父组件卸载时(通过unmountedhook du cycle de vie) Cette méthode doit être appelée pour le nettoyage.

// renderComponent.js
import { createVNode, render } from 'vue'

export default function renderComponent({ el, component, props, appContext }) {
  let vnode = createVNode(component, props)
  vnode.appContext = { ...appContext }
  render(vnode, el)

  return () => {
    // destroy vnode
    render(null, el)
    vnode = undefined
  }
}

Remarque : Cette méthode repose sur des méthodes internes (createVNoderender) qui peuvent être refactorisées ou supprimées dans les versions futures.

Démo 1

Option 2 : Utiliser createApp(component, props)app.mount(container)

Créer : Créez un createApp()createApp()创建一个应用实例。该实例具有mount() ="noreferrer">Exemples d'application . Cette instance a une méthode mount()

qui peut être used Rend le composant sur l'élément conteneur donné.

Destruction : Les instances d'application ont la méthode unmount()方法来销毁应用和组件实例。当父组件卸载时(通过unmountedunmount()

pour détruire les instances d'application et de composant. Cette méthode doit être appelée pour nettoyer lorsque le composant parent est démonté (via le hook de cycle de vie unmount).

// renderComponent.js
import { createApp } from 'vue'

export default function renderComponent({ el, component, props, appContext }) {
  let app = createApp(component, props)
  Object.assign(app._context, appContext) // 必须使用Object.assign在_context上
  app.mount(el)

  return () => {
    // 销毁app/component
    app?.unmount()
    app = undefined
  }
}
Remarque : Cette méthode crée une instance d'application pour chaque composant. Si plusieurs composants doivent être instanciés dans le document en même temps, cela peut entraîner une surcharge considérable.

Démo 2

Exemple d'utilisation🎜
<script setup>
import { ref, onUnmounted, getCurrentInstance } from 'vue'
import renderComponent from './renderComponent'

const { appContext } = getCurrentInstance()
const container = ref()
let counter = 1
let destroyComp = null

onUnmounted(() => destroyComp?.())

const insert = async () => {
  destroyComp?.()
  destroyComp = renderComponent({
    el: container.value,
    component: (await import('@/components/HelloWorld.vue')).default
    props: {
      key: counter,
      msg: 'Message ' + counter++,
    },
    appContext,
  })
}
</script>

<template>
  <button @click="insert">插入组件</button>
  <div ref="container"></div>
</template>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal