最近在做一个“新增自动播报类型”的弹框时,遇到了一个常见但容易绕晕的问题:如何根据服务端返回的数据自动勾选对应的复选框。理解了关键细节之后,这个问题就变得清晰了。这里把整个过程和思路记录下来,希望对你有帮助。

1. 需求场景简述

弹窗里有一组“播报事件类型”的复选框:

  • 所有事件类型由接口 getEventType() 返回,包含 id 和 name
  • 打开弹窗时,需要根据另一个接口 getAutoBroadcastStatus() 返回的 incidentType(事件名称数组)自动勾选已有设置
  • 点击确定时,再把选中的事件名称提交回去更新配置

这看似简单,但核心难点是:API 返回的是名称,我们当前的选中状态用的是 ID —— 怎么把两者关联起来,并让 UI 响应式更新?

2. 选中状态的结构设计

我们用 reactive 管理选中状态:

const selected = reactive({
  event: [] as number[],         // 已选事件类型的 ID 数组
  area: [] as number[],
  ship: [] as number[],
  get eventSet() { return new Set(this.event) },  // 方便判断
  get areaSet() { return new Set(this.area) },
  get shipSet() { return new Set(this.ship) },
});

重点在于这几个 getter:

  • eventSet 并不是存储的数据,而是一个“计算属性”
  • 每次访问时,它都会读取最新的 this.event
  • 即使 selected.event 在别处改变,eventSet 也会自动使用最新值生成新的 Set
  • 在模板里判断是否选中时,只需要 selected.eventSet.has(item.id) 即可,简单易懂
     

3. 数据回显的核心流程

Step 1:加载事件类型列表

先调用 getEventType() 拿到所有可能的选项:

const handleGetEventType = async () => {
  const res = await getEventType();
  if (res) {
    par.eventTypeList = res;
  }
};
Step 2:获取已勾选的事件名称数组
const res = await getAutoBroadcastStatus();
// res.incidentType = ["碰撞风险事件", "禁止进入区域事件"]
Step 3:把名称转换成 ID

因为我们的选中状态是用 ID 表示的,需要把 API 返回的名称映射到对应的 id:

const selectedIds: number[] = [];

res.incidentType.forEach((typeName: string) => {
  const matchedItem = par.eventTypeList.find(
    (item: any) => item.name === typeName
  );
  if (matchedItem) {
    selectedIds.push(matchedItem.id);
  }
});

selected.event = selectedIds;  // 例如 [1, 2]
Step 4:模板中的自动勾选
<img :src="selected.eventSet.has(item.id) ? selectedSvg : notSelectedSvg">
  • 如果当前项的 id 在 selected.event 里,getter 会生成包含该 id 的 Set,has() 结果为 true,显示勾选图标
  • 如果不在,则 has() 返回 false,显示未选中状态
Step 5:转换过程示例
// API 返回:["xxxx事件", "xxxxxx事件"]
// ↓ 查找匹配
// par.eventTypeList.find(item => item.name === "xxxx事件") 
//   → { id: 1, name: "xxxx事件" }
// par.eventTypeList.find(item => item.name === "xxxxxx事件")
//   → { id: 2, name: "xxxxxx事件" }
// ↓ 提取 id
// selectedIds = [1, 2]
// ↓ 赋值
// selected.event = [1, 2]

4. 为什么 getter 能自动更新?

将 eventSet 写成 getter:

get eventSet() { return new Set(this.event) }

原因:

  • 每次访问都会执行函数,返回基于最新 this.event 的新 Set
  • Vue 会追踪 getter 内部对响应式数据的访问(即 this.event)
  • 一旦 selected.event 更新(比如 API 回显或用户点击复选框),依赖它的模板都会重新计算
  • 无需手动同步 Set,只要更新数组即可

理解这一点非常关键,这也是“自动勾选”能顺利实现的根本。

5. 用户操作与自动回显是一致的

无论是用户点击复选框,还是 API 回显赋值,最终都是改变 selected.event:

  • 用户点击:toggleItem 中直接 push 或 splice id
  • 数据回显:selected.event = [转化后的 id 数组]

逻辑统一、数据结构一致,减少了潜在的同步问题。

6. 整体链路回顾

API 获取事件列表 → par.eventTypeList
API 返回已选事件名称 → incidentType
名称匹配 ID → selected.event = [id1, id2]
模板校验 → selected.eventSet.has(item.id)
根据结果显示勾选状态

7. 常见疑问 & 解释

Q: 为什么自动勾选?

因为模板每次渲染都会根据 selected.eventSet.has(item.id) 判断,只要 selected.event 中有那个 id,图标就显示勾选。

Q: eventSet 为什么不用普通属性?

如果写成 eventSet: new Set([]),只有初始化时计算一次,后续需要手动同步,很容易漏掉。getter 每次使用时都依赖最新的 selected.event,不会出错。

Q: 什么时候需要 await?

在 onMounted 里必须先 await handleGetEventType()(拿到全部事件列表),再去 await getSelectedEventTypes(),否则无法把名称匹配到 ID。

8. 一句话总结

“自动勾选”本质上就是:拿到服务端的数据 → 一次性设置 selected.event(ID 数组)→ 模板通过 eventSet.has(id) 判断 → Vue 响应式系统负责更新界面。

把这条链路想清楚,整个逻辑就丝滑了。希望这篇记录能帮你快速攻克类似场景。
 

9、Getter 属性的工作原理

解释 eventSet 作为 getter 的自动更新机制:

1. Getter 不是存储值,而是计算属性
const selected = reactive({
    event: [] as number[],
    get eventSet() { return new Set(this.event) },  // 这是一个 getter
});

区别:

  • 普通属性:event: [] 存储一个值
  • Getter 属性:get eventSet() 每次访问时都会执行函数并返回新值
2. 每次访问都会重新计算
// 示例代码
console.log(selected.eventSet);  // 第1次访问:执行 getter,返回 new Set([])
selected.event = [1, 2];
console.log(selected.eventSet);  // 第2次访问:执行 getter,返回 new Set([1, 2])

每次访问 selected.eventSet 时:

  1. 执行 return new Set(this.event)
  1. 创建一个新的 Set 对象
  1. 返回这个 Set

所以 eventSet 不会“更新”,而是每次访问时都会基于当前的 this.event 重新计算。

3. Vue 的响应式系统如何追踪 Getter

在模板中使用时

<img :src="selected.eventSet.has(item.id) ? selectedSvg : notSelectedSvg">

Vue 的响应式系统会:

  • 首次渲染时访问 selected.eventSet.has(item.id)
  • 访问 selected.eventSet 触发 getter
  • getter 内部访问 this.event,Vue 记录依赖关系:
  •  模板表达式 → 依赖 → selected.eventSet (getter) → 依赖 → selected.event
  • 当 selected.event 改变时,Vue 知道需要重新计算依赖它的表达式
  • 重新渲染时,再次访问 selected.eventSet,getter 基于新的 this.event 返回新的 Set
4. 实际执行流程示例
// 初始状态
selected.event = []
// 模板访问:selected.eventSet.has(1)
// → 触发 getter: return new Set([])
// → Set([]).has(1) = false
// → 显示未选中图标

// 用户操作或数据回显
selected.event = [1, 2]
// Vue 检测到 selected.event 改变
// → 标记依赖它的表达式需要重新计算
// → 重新渲染模板
// → 再次访问:selected.eventSet.has(1)
// → 触发 getter: return new Set([1, 2])  ← 注意:这是新的 Set!
// → Set([1, 2]).has(1) = true
// → 显示选中图标
5. 为什么使用 Getter 而不是普通属性?

如果使用普通属性:

// ❌ 错误的方式
const selected = reactive({
    event: [],
    eventSet: new Set([]),  // 普通属性,只初始化一次
});

// 当 event 改变时,需要手动更新 eventSet
selected.event = [1, 2];
selected.eventSet = new Set([1, 2]);  // 必须手动同步!

使用 Getter 的好处:

// ✅ 正确的方式
const selected = reactive({
    event: [],
    get eventSet() { return new Set(this.event) },  // 自动同步
});

// 当 event 改变时,eventSet 自动反映最新状态
selected.event = [1, 2];
// 下次访问 selected.eventSet 时,自动返回 new Set([1, 2])
6. 可视化理解

┌─────────────────────────────────────────┐
│  selected.event = [1, 2]               │  ← 数据源(响应式)
└─────────────────────────────────────────┘
              ↓ (依赖追踪)
┌─────────────────────────────────────────┐
│  get eventSet() {                       │  ← Getter(计算属性)
│    return new Set(this.event)          │    每次访问都重新计算
│  }                                      │
└─────────────────────────────────────────┘
              ↓ (模板中使用)
┌─────────────────────────────────────────┐
│  selected.eventSet.has(item.id)         │  ← 模板表达式
└─────────────────────────────────────────┘

7. 关键点总结
  1. Getter 不是存储值,而是计算函数
  2. 每次访问都会重新执行,返回基于当前数据的新值
  3. Vue 会追踪 getter 内部访问的响应式数据(如 this.event)
  4. 当依赖的响应式数据改变时,Vue 会重新计算依赖它的表达式
  5. 这样实现了自动同步,无需手动更新

这就是为什么 selected.eventSet 能自动反映 selected.event 的最新状态。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐