Vue3 Composition API中如何实现v-for下SFC组件的依次渲染
问题描述
我创建了两个文件来演示遇到的问题:
Parent.vue
<script setup> import { ref } from "vue"; import TypeWriter from "./TypeWriter.vue"; const strings = ref([ "first_string", "second_string", "third_string" ]); </script> <template> <div> <div v-for="string in strings"> <TypeWriter :string="string" /> </div> </div> </template>
TypeWriter.vue
<script setup> import { ref, onMounted } from "vue"; const props = defineProps({ string: String }); const messageWrapper = ref(null); function printMessage() { const target = messageWrapper.value; const message = props.string; // 逐字打印消息到 div.typewriter-message 的代码 } onMounted(() => { printMessage(messageWrapper.value); }); </script> <template> <div class="typewriter-message" ref="messageWrapper"></div> </template>
目前的问题是:TypeWriter组件需要时间逐字渲染内容,但v-for会一次性生成所有组件,导致三个组件同时开始渲染。我希望实现逐个渲染——下一个组件在上一个完全渲染完成后才开始执行打字效果。
我曾尝试把strings定义为带回调的对象,也试过async/await组合,但都没成功,可能是对Vue组件渲染机制理解不够。
实现思路与解决方案
核心思路
通过父组件控制子组件的启动时机:
- 子组件完成打字后,向父组件发送完成通知
- 父组件维护一个允许启动的索引阈值,只有组件索引小于等于该阈值时,才触发打字逻辑
- 收到子组件的完成通知后,递增阈值,启动下一个组件的打字效果
具体实现
1. 修改 TypeWriter.vue,添加启动控制与完成事件
<script setup> import { ref, watch } from "vue"; const props = defineProps({ string: String, // 新增:控制是否开始打字 start: { type: Boolean, default: false } }); // 新增:定义完成事件,通知父组件 const emit = defineEmits(['complete']); const messageWrapper = ref(null); async function printMessage() { if (!props.start) return; const target = messageWrapper.value; const message = props.string; target.textContent = ''; // 替换成你实际的逐字打印逻辑 for (let char of message) { target.textContent += char; await new Promise(resolve => setTimeout(resolve, 150)); } // 打字完成后通知父组件 emit('complete'); } // 监听start状态变化,触发打字 watch(() => props.start, (newVal) => { if (newVal) printMessage(); }); </script> <template> <div class="typewriter-message" ref="messageWrapper"></div> </template>
2. 修改 Parent.vue,控制组件启动节奏
<script setup> import { ref, onMounted } from "vue"; import TypeWriter from "./TypeWriter.vue"; const strings = ref([ "first_string", "second_string", "third_string" ]); // 当前允许启动的组件最大索引 const currentActiveIndex = ref(-1); // 处理子组件完成事件,启动下一个 const handleTypeComplete = () => { currentActiveIndex.value += 1; }; // 页面加载时启动第一个组件 onMounted(() => { currentActiveIndex.value = 0; }); </script> <template> <div> <div v-for="(string, index) in strings" :key="index"> <TypeWriter :string="string" :start="index <= currentActiveIndex" @complete="handleTypeComplete" /> </div> </div> </template>
简化方案(适合不需要保留全部组件DOM的场景)
如果不需要同时展示所有打字完成的内容,可只渲染当前正在打字的组件,完成后切换内容:
<script setup> import { ref, onMounted } from "vue"; import TypeWriter from "./TypeWriter.vue"; const strings = ref([ "first_string", "second_string", "third_string" ]); const currentStringIndex = ref(0); const handleTypeComplete = () => { if (currentStringIndex.value < strings.value.length - 1) { currentStringIndex.value += 1; } }; </script> <template> <div> <TypeWriter :string="strings[currentStringIndex]" :start="true" @complete="handleTypeComplete" /> </div> </template>
关键说明
- 子组件通过
startprops控制启动时机,避免了挂载即执行的问题 - 父组件利用响应式索引,自动触发下一个组件的启动状态
- 整个流程通过事件通知+响应式状态更新实现,完全契合Vue的渲染机制
内容的提问来源于stack exchange,提问作者Aigi
相关产品推荐
相关产品推荐

