为FAQ组件的questions Prop添加响应式isExpanded属性的最佳实践
FAQ组件展开/折叠状态的最佳实践
你的需求是给FAQ组件的每个问答项添加展开/折叠状态,用JSON.parse(JSON.stringify)克隆Prop的方案不是最佳实践,原因和更优方案如下:
为什么不推荐克隆Prop的方案
- 性能隐患:
JSON.parse(JSON.stringify)是深拷贝操作,当questions数组较大或频繁更新时,会带来不必要的性能开销。 - 类型丢失:如果问答对象中包含函数、Date、Symbol等JSON无法序列化的类型,克隆后这些数据会被直接破坏。
- 逻辑冗余:额外维护一份与原Prop高度重合的数据,会增加组件的逻辑复杂度,也不符合Vue推荐的"状态与原始数据分离"的设计思路。
更优方案:单独维护展开状态数组
不需要修改或克隆原Prop,而是用一个独立的ref数组来记录每个问答项的展开状态,完全遵循单向数据流原则:
const props = defineProps({ questions: { type: Array as () => { question: string; answer: string; }[], default: () => [], }, }); // 初始化展开状态,默认全部折叠 const expandedStates = ref(props.questions.map(() => false)); // 切换指定项的展开状态 const toggleExpand = (index: number) => { expandedStates.value[index] = !expandedStates.value[index]; };
在模板中通过索引关联原问答数据和展开状态即可:
<template> <div v-for="(item, index) in questions" :key="index" class="faq-item"> <button @click="toggleExpand(index)" class="faq-toggle"> {{ item.question }} {{ expandedStates[index] ? ' ▲' : ' ▼' }} </button> <div v-show="expandedStates[index]" class="faq-answer"> {{ item.answer }} </div> </div> </template>
进阶:派生带状态的问答数据(可选)
如果需要在组件内传递包含展开状态的完整问答对象,可以用computed派生数据,避免直接修改原Prop:
const props = defineProps({ questions: { type: Array as () => { question: string; answer: string; }[], default: () => [], }, }); const expandedStates = ref(props.questions.map(() => false)); // 派生包含展开状态的问答项数组 const faqItems = computed(() => { return props.questions.map((item, index) => ({ ...item, isExpanded: expandedStates.value[index] })); }); const toggleExpand = (index: number) => { expandedStates.value[index] = !expandedStates.value[index]; };
这种方式既保留了原Prop的纯净性,又能方便地使用带状态的数据,适合复杂组件场景。
内容的提问来源于stack exchange,提问作者Mads Hjorth
相关产品推荐
相关产品推荐

