javascript Vue 3将组件附加到DOM:最佳实践

mwg9r5ms  于 2023-03-16  发布在  Java
关注(0)|答案(3)|浏览(257)

我想在我的Vue 3应用程序中动态创建一个SFC中的组件,并将其附加到DOM中。我使用的是<script setup>样式的组件,这是另一个问题。
这似乎是不必要的困难。
以下是我大致想做的事情:
1.获取一些数据。明白。
1.创建Vue组件的示例:Foo.vue.
1.把数据作为 prop 交给它。
1.将其附加到DOM中我想要的位置。
问题是我不能在模板中执行〈component:is=“Foo:〉,因为我不知道它将在模板呈现很久之后的位置。
对此有什么最好的实践吗?一个简单的例子,一些善良的灵魂可以提供将是巨大的赞赏。
有一半的时间我不能从Vue文档中看出头绪。对不起,我不想这么说,但是它们对Vue的新手来说是相当不透明的,让我觉得很笨。
下面是一些模拟代码,说明了我想做的事情

  1. import Foo from "../components/Foo.vue"
  2. function makeAFoo(p, data){
  3. // instantiate my Foo.vue (not sure how to do this inline), and pass it the data it needs
  4. let foo = new Foo(data); // if only it were this simple, right?
  5. // Append it to p (which is an HTML Element)
  6. p.appendChild(foo)
  7. }
s2j5cfk0

s2j5cfk01#

选项1:createVNode(component, props)render(vnode, container)

**创建:**使用createVNode()来创建具有props的组件定义的VNode(例如,从*.vue导入的SFC),其可以被传递到render()以在给定容器元素上呈现它。
**销毁:**调用render(null, container)将销毁附加到容器的VNode。在父组件卸载时(通过unmounted生命周期挂接),应将此调用作为清理。

  1. // renderComponent.js
  2. import { createVNode, render } from 'vue'
  3. export default function renderComponent({ el, component, props, appContext }) {
  4. let vnode = createVNode(component, props)
  5. vnode.appContext = { ...appContext }
  6. render(vnode, el)
  7. return () => {
  8. // destroy vnode
  9. render(null, el)
  10. vnode = undefined
  11. }
  12. }

**警告:**此方法依赖于内部方法(createVNoderender),在未来版本中可能会重构或删除这些方法。

demo 1

选项2:createApp(component, props)app.mount(container)

**正在创建:**使用createApp(),它返回一个应用程序示例。该示例具有mount(),可用于呈现给定容器元素上的组件。
**销毁:**应用程序示例具有unmount()以销毁应用程序和组件示例。在父组件卸载时(通过unmounted生命周期挂接),应将此调用为清理。

  1. // renderComponent.js
  2. import { createApp } from 'vue'
  3. export default function renderComponent({ el, component, props, appContext }) {
  4. let app = createApp(component, props)
  5. Object.assign(app._context, appContext) // must use Object.assign on _context
  6. app.mount(el)
  7. return () => {
  8. // destroy app/component
  9. app?.unmount()
  10. app = undefined
  11. }
  12. }

**警告:**这种方法为每个组件创建一个应用程序示例,如果需要在文档中同时示例化许多组件,那么这可能会带来不小的开销。

demo 2

用法示例

  1. <script setup>
  2. import { ref, onUnmounted, getCurrentInstance } from 'vue'
  3. import renderComponent from './renderComponent'
  4. const { appContext } = getCurrentInstance()
  5. const container = ref()
  6. let counter = 1
  7. let destroyComp = null
  8. onUnmounted(() => destroyComp?.())
  9. const insert = async () => {
  10. destroyComp?.()
  11. destroyComp = renderComponent({
  12. el: container.value,
  13. component: (await import('@/components/HelloWorld.vue')).default
  14. props: {
  15. key: counter,
  16. msg: 'Message ' + counter++,
  17. },
  18. appContext,
  19. })
  20. }
  21. </script>
  22. <template>
  23. <button @click="insert">Insert component</button>
  24. <div ref="container"></div>
  25. </template>
展开查看全部
hxzsmxv2

hxzsmxv22#

更简单的方法是使用v-if或v-for。
与直接处理组件不同,您可以处理组件的状态,并让VueReact性发挥作用
下面是一个动态附加组件(吐司)的示例,同时只操作组件的状态

吐司.vue文件:v-for在这里是被动的,每当一个新的错误被添加到错误的对象中时,它就会被呈现

  1. <script setup lang="ts">
  2. import { watch } from 'vue';
  3. import { ref, onUpdated } from 'vue';
  4. import { Toast } from 'bootstrap';
  5. const props = defineProps({
  6. errors: { type: Array, default: () => [] },
  7. });
  8. onUpdated(() => {
  9. const hiddenToasts = props.errors.filter((obj) => {
  10. return obj.show != true;
  11. });
  12. hiddenToasts.forEach(function (error) {
  13. var errorToast = document.getElementById(error.id);
  14. var toast = new Toast(errorToast);
  15. toast.show();
  16. error.show = true;
  17. errorToast.addEventListener('hidden.bs.toast', function () {
  18. const indexOfObject = props.errors.findIndex((item) => {
  19. return item.id === error.id;
  20. });
  21. if (indexOfObject !== -1) {
  22. props.errors.splice(indexOfObject, 1);
  23. }
  24. });
  25. });
  26. });
  27. </script>
  28. <script lang="ts">
  29. const TOASTS_MAX = 5;
  30. export function push(array: Array, data): Array {
  31. if (array.length == TOASTS_MAX) {
  32. array.shift();
  33. array.push(data);
  34. } else {
  35. array.push(data);
  36. }
  37. }
  38. </script>
  39. <template>
  40. <div
  41. ref="container"
  42. class="position-fixed bottom-0 end-0 p-3"
  43. style="z-index: 11"
  44. >
  45. <div
  46. v-for="item in errors"
  47. v-bind:id="item.id"
  48. class="toast fade opacity-75 bg-danger"
  49. role="alert"
  50. aria-live="assertive"
  51. aria-atomic="true"
  52. data-bs-delay="15000"
  53. >
  54. <div class="toast-header bg-danger">
  55. <strong class="me-auto text-white">Error</strong>
  56. <button
  57. type="button"
  58. class="btn-close"
  59. data-bs-dismiss="toast"
  60. aria-label="Close"
  61. ></button>
  62. </div>
  63. <div class="toast-body text-white error-body">{{ item.msg }}</div>
  64. </div>
  65. </div>
  66. </template>

错误触发值:在这里,每当单击发生时,我们都会将错误推送到错误的对象

  1. <script setup lang="ts">
  2. import { ref, reactive } from 'vue';
  3. import toast from './Toast.vue';
  4. import { push } from './Toast.vue';
  5. const count = ref(0);
  6. const state = reactive({ errors: [] });
  7. function pushError(id: int) {
  8. push(state.errors, { id: id, msg: 'Error message ' + id });
  9. }
  10. </script>
  11. <template>
  12. <toast :errors="state.errors"></toast>
  13. <button type="button" @click="pushError('toast' + count++)">
  14. Error trigger: {{ count }}
  15. </button>
  16. </template>

完整示例如下:https://stackblitz.com/edit/vitejs-vite-mcjgkl

展开查看全部
3yhwsihp

3yhwsihp3#

mount-vue-component包提供了一个helper函数,该函数使用@tony19答案中的选项1。
一个警告:如果组件是在.vue文件中定义的,并且只是通过编程创建的,那么您可能需要导入并注册它,否则树抖动可能会删除它的模板,导致在生产时插入一个空组件(这个问题似乎主要影响Vue + Vite)。
例如:

  1. import MyComponent from "./MyComponent.vue";
  2. const app = createApp(App).use(router);
  3. app.component("MyComponent", MyComponent);

相关问题