最佳實務:Vue 3 中將元件附加到 DOM 的方法
P粉362071992
P粉362071992 2023-08-24 19:51:03
0
2
616
<p>我想在我的Vue 3應用程式中動態建立一個元件,該元件在單一檔案元件(SFC)中,並將其附加到DOM。我正在使用<code><script setup></code>風格的組件,這是另一個問題。 </p> <p>這似乎是不必要的困難。 </p> <p>以下大致是我想做的事:</p> <ol> <li>取得一些數據。已經完成。 </li> <li>建立一個Vue元件的實例:Foo.vue。 </li> <li>將資料作為屬性傳遞給它。 </li> <li>將它附加到我想要的位置。 </li> </ol> <p>問題是,我不能在模板中使用<component :is="Foo:>,因為在模板渲染之後很久之後,我不知道它將在哪裡。</p> <p>有沒有最佳實務?有沒有善心人士可以提供一個簡單的例子,我會非常感激。 </p> <p>我有時無法理解Vue文檔的一半時間。抱歉,不想這麼說,但對於Vue的新手來說,它們相當晦澀,讓我感到很愚蠢。 </p> <p>以下是一些假程式碼,說明我想做的事情:</p> <pre class="brush:php;toolbar:false;">import Foo from "../components/Foo.vue" function makeAFoo(p, data){ // 實例化我的Foo.vue(不確定如何在內聯中實作),並將其傳遞所需的數據 let foo = new Foo(data); // 如果只有這麼簡單,對吧? // 將其附加到p(這是一個HTML元素) p.appendChild(foo) }</pre> <p><br /></p>
P粉362071992
P粉362071992

全部回覆(2)
P粉004287665

更簡單的方法是使用v-if或v-for。

與其直接處理組件,不如處理組件的狀態,讓Vue的響應性魔法發揮作用

這是一個範例,動態新增一個元件(Toast),只需操作元件的狀態

Toast.vue檔案:這裡的v-for是響應式的,每當向errors物件新增新的錯誤時,它將被渲染

<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:每當發生點擊事件時,我們向errors物件推送一個錯誤

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

完整範例: https://stackblitz.com/edit/vitejs-vite-mcjgkl

P粉598140294

選項1:使用createVNode(component, props)render(vnode, container)

建立:使用createVNode()建立一個帶有props的元件定義的VNode(例如,從*.vue導入的SFC),可以將其傳遞給render()在給定的容器元素上渲染。

銷毀:呼叫render(null, container)會銷毀附加到容器的VNode。當父元件卸載時(透過unmounted生命週期鉤子)應該呼叫此方法進行清理。

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

注意:此方法依賴內部方法(createVNoderender),這些方法在未來的版本中可能會進行重構或刪除。

示範1

選項2:使用createApp(component, props)app.mount(container)

#建立:使用createApp()建立一個應用程式實例。此實例具有mount()方法,可用於在給定的容器元素上渲染元件。

銷毀:應用實例具有unmount()方法來銷毀應用程式和元件實例。當父元件卸載時(透過unmounted生命週期鉤子)應該呼叫此方法進行清理。

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

注意:此方法為每個元件建立一個應用實例,如果需要在文件中同時實例化多個元件,可能會產生不小的開銷。

示範2

範例用法

<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>
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板