You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 10:01:43