搜尋

首頁  >  問答  >  主體

最佳實務:Vue 3 中將元件附加到 DOM 的方法

<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粉362071992P粉362071992451 天前593

全部回覆(2)我來回復

  • P粉004287665

    P粉0042876652023-08-25 18:25:26

    更簡單的方法是使用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

    回覆
    0
  • P粉598140294

    P粉5981402942023-08-25 00:33:13

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

    回覆
    0
  • 取消回覆