省流總結:(具體實現見下方)
vue 組件?——》<component :is='組件名'>?
htmlelement 元素?——》?ref 、★?v-for + ref 或是 ★?vue 的 nextTick
純 html 結構——》v-html?
另外,當數據異步加載時,vue3中如何渲染:①watch監聽;②異步加載+條件渲染;③?watch(監聽對象, async(newValue)=>{... await nextTick() }
vue 組件的?動態渲染?——》 <component :is="組件名"
<component :is="componentName"></component>
是 Vue 中的動態組件用法,它允許你根據某個條件動態地渲染不同的組件。
不適合原生的 DOM 元素。
其中,
:is
??指令 讓你能夠指定要渲染的組件類型
componentName
是一個字符串,表示你要渲染的組件的名稱,或者是一個組件對象。可以是本地注冊的組件名稱,或者一個導入的組件對象。
?例子:
<template><div><button @click="setComponent('componentA')">Show Component A</button><button @click="setComponent('componentB')">Show Component B</button><!-- 動態渲染組件 --><component :is="currentComponent"></component></div>
</template><script>
import ComponentA from './ComponentA.vue';
import ComponentB from './ComponentB.vue';export default {data() {return {currentComponent: 'ComponentA', // 初始渲染ComponentA};},methods: {setComponent(componentName) {this.currentComponent = componentName;},},components: {ComponentA,ComponentB,},
};
</script>
在這個例子中,<component :is="currentComponent">
會根據 currentComponent
的值動態切換 ComponentA
或 ComponentB
。?
htmlelement 元素——》 ref??渲染
將 HTMLElement
通過 ref
引用傳遞給 Vue,直接操作 DOM 結構,將 HTMLElement
添加到某個元素中。適用于需要直接操作 DOM 的情況(更靈活)。
例子:
<template><div ref="container"></div> <!-- 渲染到指定的 DOM 元素 -->
</template><script>
import { store } from './store';export default {mounted() {this.renderElement();},methods: {renderElement() {const container = this.$refs.container;const element = store.state.component;if (container && element) {container.appendChild(element); // 將 HTMLElement 添加到指定的 ref 元素中}},},
};
</script>
使用 v-for
+ ref
渲染 HtmlElement 元素
實現:渲染component屬性,canvasItems數組中,每個對象中都有一個component屬性(即 htmlElement?元素)
vue2版本:
// store.js
export const store = {state: {canvasItems: [{id: 1,component: document.createElement('div'), // 假設這是 HTML 元素type: 'image',properties: { /* 其他屬性 */ }},{id: 2,component: document.createElement('button'), // 另一個 HTML 元素type: 'button',properties: { /* 其他屬性 */ }}]}
};
?vue 組件中,通過 v-for 渲染每個 component:
<template><div><!-- 遍歷 canvasItems 渲染每個 component --><div v-for="(item, index) in canvasItems" :key="item.id"><!-- 渲染對應的 HTMLElement --><div :ref="`container-${index}`"></div></div></div>
</template><script>
import { store } from './store';export default {computed: {canvasItems() {return store.state.canvasItems; // 獲取 store 中的 canvasItems 數據}},mounted() {this.renderCanvasItems();},methods: {renderCanvasItems() {// 遍歷 canvasItems 并渲染每個 HTMLElementthis.canvasItems.forEach((item, index) => {const container = this.$refs[`container-${index}`]; // 獲取每個 item 對應的 refif (container && item.component) {container.appendChild(item.component); // 將 HTMLElement 插入到指定位置}});}}
};
</script>
代碼解釋:
v-for
遍歷:我們使用v-for
循環遍歷canvasItems
數組中的每個元素。
ref
動態綁定:ref
的值通過?模板字符串\
container-${index}`?來動態生成不同的?
ref` 名稱,以便在方法中獲取每個對應的容器。
appendChild
插入 DOM:在mounted
鉤子中,我們通過this.$refs
獲取到每個容器,并將component
(即HTMLElement
)插入到對應的容器中。注:vue3 語法糖中,可以把 this.$refs??換成? const xxx = ref()?
vue3 <script setup>?語法糖版本:
<template><div><div v-for="(item, index) in items" :key="item.id"><!-- 使用函數形式的 ref --><div :ref="(el) => setContainerRef(el, index)"></div></div></div>
</template><script setup>
import { ref, onMounted } from 'vue'
import { canvasItems } from './store'// 存儲 DOM 引用的容器
const containerRefs = ref([])
const items = ref(canvasItems.value || []); // 若 canvansItems 是響應式引用Ref,則可以不加這一步。// 設置引用的函數
const setContainerRef = (el, index) => {if (el) {containerRefs.value[index] = el}
}onMounted(() => {// 確保 canvasItems 已正確賦值if (canvasItems.value) {// 遍歷 canvasItems 數組并訪問每個 item 的 component 屬性canvasItems.value.forEach((item, index) => {const container = containerRefs.value[index]if (container && item.component) {container.appendChild(item.component) // 將 HTMLElement 插入到指定容器}})} else {console.error('canvasItems is not defined or empty')}
})
</script>
關鍵變化說明:
移除了?
this
?的使用,改用?響應式變量使用?
:ref="(el) => setContainerRef(el, index)"
?替代字符串形式的 ref?使用?
containerRefs
?數組?來存儲 DOM 引用將?
mounted
?生命周期鉤子改為?onMounted
移除了?
computed
?屬性,直接在 setup 中聲明響應式變量
★?vue3 <script setup> 進階版本:?數據是異步加載的。(onMounted 掛載時 數據還沒有準備好)
法1:使用?watch
?監聽?數據變化——》watch(監聽對象,(newValue)=>{響應},? {deep:true 開啟深度監聽})
<script setup>
import { ref, watch, onMounted } from 'vue'
import { canvasItems } from './store'const containerRefs = ref([])
const isMounted = ref(false)const setContainerRef = (el, index) => {if (el) containerRefs.value[index] = el
}// 監聽 canvasItems 變化
watch(canvasItems, (newItems) => {if (!newItems || !isMounted.value) returnnewItems.forEach((item, index) => {const container = containerRefs.value[index]if (container && item.component) {container.appendChild(item.component)}})
}, { deep: true })onMounted(() => {isMounted.value = true// 如果數據已經加載,立即執行if (canvasItems.value) {canvasItems.value.forEach((item, index) => {const container = containerRefs.value[index]if (container && item.component) {container.appendChild(item.component)}})}
})
</script>
法2:使用?異步加載 + 條件渲染 ——》async ... await new Promise( res=>{})? + nextTick()
<template><div v-if="loaded"><div v-for="(item, index) in items" :key="item.id"><div :ref="(el) => setContainerRef(el, index)"></div></div></div><div v-else>Loading...</div>
</template><script setup>
import { ref, onMounted } from 'vue'
import { canvasItems } from './store'const containerRefs = ref([])
const loaded = ref(false)
const items = ref([])const setContainerRef = (el, index) => {if (el) containerRefs.value[index] = el
}// 假設有一個異步加載函數
const loadData = async () => {// 這里可能是從API加載數據await new Promise(resolve => setTimeout(resolve, 500)) // 模擬異步items.value = canvasItems.value || []loaded.value = truenextTick(() => {items.value.forEach((item, index) => {const container = containerRefs.value[index]if (container && item.component) {container.appendChild(item.component)}})})
}onMounted(loadData)
</script>
?法3:使用?nextTick
?確保DOM更新 ——》 watch(監聽對象,async(newValue)=>{... await nextTick() //等待 DOM 更新})?
<script setup>
import { ref, watch, nextTick } from 'vue'
import { canvasItems } from './store'const containerRefs = ref([])const setContainerRef = (el, index) => {if (el) containerRefs.value[index] = el
}watch(canvasItems, async (newItems) => {if (!newItems) returnawait nextTick() // 等待DOM更新newItems.forEach((item, index) => {const container = containerRefs.value[index]if (container && item.component) {container.appendChild(item.component)}})
}, { immediate: true, deep: true })
</script>
?注: await nextTick() // 等待DOM更新
最佳實踐建議:
如果數據來自API,使用方案2的?異步加載模式
如果數據來自?store且可能動態變化,使用方案1或方案3
確保在操作DOM前使用?
nextTick
?等待DOM更新完成根據你的具體場景選擇最適合的方案。如果是畫布應用,通常方案1的watch方式最為可靠。
應用 - 具體實踐:
DOM元素 ——》用 onMounted??鉤子 手動直接操作 DOM
如果你需要在 Vue 渲染完成后 動態插入 DOM 元素,可以考慮使用 mounted
鉤子來手動操作 DOM:
<template><div><!-- 渲染容器 --><div v-for="(item, index) in canvasItems" :key="item.id" :ref="'container-' + index"></div></div>
</template><script>
import { store } from './store';export default {computed: {canvasItems() {return store.state.canvasItems;}},mounted() {this.renderCanvasItems();},methods: {renderCanvasItems() {this.canvasItems.forEach((item, index) => {const container = this.$refs[`container-${index}`];if (container && item.component) {container.appendChild(item.component); // 將 HTMLElement 插入到 DOM 中}});}}
};
</script>
純 HTML 字符串——》?v-html??指令 渲染? outerHTML
通過 v-html
指令,將 HTMLElement
轉換為 HTML 字符串?后插入到頁面。請注意,v-html
僅適用于?純 HTML 字符串?的渲染,并不會解析 Vue 組件或 HTMLElement
function getHtmlContent(element) {console.log('getHtmlContent==========element=', element);return element ? element.outerHTML : ''; // 將 HTMLElement 轉換為字符串
}
?然,在模板中使用 v-html?
<div v-for="item in canvasItems" v-html="getHtmlContent(item. Component)" :key="item.id" ></div>
參考:vue3 | Vue2代碼轉換成Vue3_vue2轉vue3-CSDN博客