Live Demo 展示

在笔记里直接嵌 Playground 的好处:边读边改。讲响应式时,光看代码不如让你点一下按钮看它重算几次。

1. 响应式基础:ref 与事件绑定

最小可跑的响应式示例,点按钮看 count 如何在模板里自动更新:

<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <div class="box">
    <h3>点一下按钮观察计数</h3>
    <button @click="count++">当前 {{ count }}</button>
  </div>
</template>

<style scoped>
.box {
  padding: 24px;
  text-align: center;
  font-family: system-ui;
}
button {
  padding: 8px 20px;
  border: 1px solid #8b5cf6;
  border-radius: 6px;
  background: #8b5cf6;
  color: white;
  cursor: pointer;
}
</style>

改代码看看:把 ref 换成普通变量,计数会失效。

2. computed 的缓存

讲响应式缓存时最直观的演示:同样的表达式,computed 只在依赖变化时重算,method 每次渲染都跑。

改改输入框,对比两行的执行次数:

<script setup>
import { ref, computed } from 'vue'

const text = ref('hello')
const other = ref(0)

let computedRuns = 0
let methodRuns = 0

const upper = computed(() => {
  computedRuns++
  return text.value.toUpperCase()
})

function upperMethod() {
  methodRuns++
  return text.value.toUpperCase()
}
</script>

<template>
  <div class="box">
    <label>
      输入框:
      <input v-model="text" />
    </label>
    <p>computed: {{ upper }} (实际执行 {{ computedRuns }} 次)</p>
    <p>method: {{ upperMethod() }} (实际执行 {{ methodRuns }} 次)</p>
    <p>其他状态:{{ other }}</p>
    <button @click="other++">改动无关状态,观察 method 被重跑</button>
  </div>
</template>

<style scoped>
.box { padding: 20px; font-family: system-ui; }
input { padding: 4px 8px; }
button {
  margin-top: 8px;
  padding: 6px 14px;
  border: 1px solid #8b5cf6;
  border-radius: 4px;
  background: white;
  cursor: pointer;
}
</style>

Java 类比computed 像带 @Cacheable 的方法,入参不变直接返回缓存;method 像没缓存的普通方法,调用一次执行一次。

3. v-for 的 key 影响状态复用

这是 key 值踩坑的经典现场:在每个输入框里打点字,然后点「插入到头部」,观察状态错位。

<script setup>
import { ref } from 'vue'

const list = ref([
  { id: 1, name: 'A' },
  { id: 2, name: 'B' },
  { id: 3, name: 'C' },
])

function insertTop() {
  list.value.unshift({ id: Date.now(), name: '新' })
}
</script>

<template>
  <div class="box">
    <p>在每个输入框里打几个字,再点「插入到头部」,左边会错位</p>
    <button @click="insertTop">插入到头部</button>
    <div class="grid">
      <div>
        <h4>key=index(错误)</h4>
        <div v-for="(it, i) in list" :key="i" class="row">
          <span>{{ it.name }}</span>
          <input placeholder="打点字" />
        </div>
      </div>
      <div>
        <h4>key=id(正确)</h4>
        <div v-for="it in list" :key="it.id" class="row">
          <span>{{ it.name }}</span>
          <input placeholder="打点字" />
        </div>
      </div>
    </div>
  </div>
</template>

<style scoped>
.box { padding: 20px; font-family: system-ui; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 12px; }
.row { display: flex; align-items: center; gap: 8px; margin: 4px 0; }
input { padding: 2px 6px; flex: 1; }
button { padding: 6px 14px; border: 1px solid #8b5cf6; border-radius: 4px; background: white; cursor: pointer; }
</style>

结论:v-forkey 必须能稳定标识一个项,index 不是稳定标识——列表顺序一变,key 就和 DOM 节点错位,已输入的内容会跟错条目。

语法说明

只要把代码块语言写成 vue live 就会变成沙箱:

```vue live height=400px
<script setup>
// 你的代码
</script>
```

参数:

普通 vue 代码块不会变沙箱,只是普通高亮 + 右上角多一个「在 Playground 打开」按钮,点一下也能送到独立页里玩。

延伸阅读