阶段1:造个盒子
目标:创建 reactive 对象
操作:
// 导包
import { reactive } from 'vue'
// 操作代码
let ren = reactive({ name: '强强' })
模板(<template>)里写:
{{ ren }}
结果: 页面显示 { "name": "强强" }
错误示例:
let ren = reactive('强强')
// 后果:reactive 不接受文字,只接受对象
傻瓜口令:"reactive 专门装对象"
阶段2:改盒子里的值
目标:修改属性(体验没有 .value 的爽快)
操作:
// 导包
import { reactive } from 'vue'
// 操作代码
let ren = reactive({ name: '强强' })
ren.name = '壮壮'
模板(<template>)里写:
{{ ren.name }}
结果: 页面文字变成 壮壮
错误示例:
ren.value.name = '壮壮'
// 后果:报错,reactive 没有 .value
傻瓜口令:"reactive 省事,不点 value"
阶段3:盒子套盒子
目标:修改深层属性
操作:
// 导包
import { reactive } from 'vue'
// 操作代码
let ren = reactive({
info: { city: '上海' }
})
ren.info.city = '北京'
模板(<template>)里写:
{{ ren.info.city }}
结果: 上海 变成 北京
错误示例:
// 只要是对象,藏多深都能改,没啥容易错的
傻瓜口令:"reactive 套娃,层层都能改"
阶段4:造个列表
目标:reactive 包装数组
操作:
// 导包
import { reactive } from 'vue'
// 操作代码
let paiDui = reactive(['猫', '狗'])
模板(<template>)里写:
{{ paiDui }}
结果: 页面显示 [ "猫", "狗" ]
傻瓜口令:"reactive 也能装列表"
阶段5:列表加东西
目标:往列表 push
操作:
// 导包
import { reactive } from 'vue'
// 操作代码
let paiDui = reactive(['猫', '狗'])
paiDui.push('鸟')
模板(<template>)里写:
{{ paiDui }}
结果: 页面显示 [ "猫", "狗", "鸟" ]
错误示例:
paiDui.value.push('鸟')
// 后果:报错,没有 .value
傻瓜口令:"列表加东西,直接 push"
阶段6:不吃数字
目标:验证 reactive 不能包数字
操作:
// 导包
import { reactive } from 'vue'
// 操作代码
let shuZi = reactive(100)
// 试图修改
shuZi = 200
模板(<template>)里写:
{{ shuZi }}
结果: 页面一直显示 100,根本不会变,因为 100 不是对象,reactive 没法让它“活”过来
错误示例:
let b = reactive(true)
// 后果:boolean 也不是对象,无效
傻瓜口令:"数字文字别找我,去找 ref"
阶段7:列表删项 (splice)
目标:在 reactive 数组里删东西
操作:
// 导包
import { reactive } from 'vue'
// 操作代码
let paiDui = reactive(['猫', '狗', '鸟'])
// 参数1:从第几个开始删(下标从0开始)
// 参数2:删几个
paiDui.splice(1, 1) // 删掉 '狗'
模板(<template>)里写:
{{ paiDui }}
结果: 页面显示 [ "猫", "鸟" ]
错误示例:
paiDui = paiDui.filter(...)
// 后果:这是赋值新数组,reactive 会断连!(后面 L3 会细讲)
傻瓜口令:"列表要删项,记得用 splice"
阶段8:帮忙算术
目标:Computed 配合 reactive
操作:
// 导包
import { reactive, computed } from 'vue'
// 操作代码
let ren = reactive({ age: 10 })
let nextAge = computed(() => ren.age + 1)
模板(<template>)里写:
{{ nextAge }}
结果: 页面显示 11
傻瓜口令:"computed 帮对象自动算"
阶段9:盯着一个看
目标:Watch 监听单个属性
操作:
// 导包
import { reactive, watch } from 'vue'
// 操作代码
let ren = reactive({ age: 10, name: '强强' })
// 重点:必须用 () => ren.age
watch(() => ren.age, () => console.log('年龄变了'))
ren.age = 11
模板(<template>)里写:
看控制台
结果: 控制台打印:年龄变了
错误示例:
watch(ren.age, ...)
// 后果:ren.age 是数字 10,watch 听不到数字变化,必须听“函数”
傻瓜口令:"watch 盯属性,要用箭头指"
阶段10:盯着全家看
目标:Watch 监听整个对象
操作:
// 导包
import { reactive, watch } from 'vue'
// 操作代码
let ren = reactive({ age: 10, name: '强强' })
// 直接传对象,默认任何属性变了都能听到
watch(ren, () => console.log('家里有动静'))
ren.name = '壮壮'
结果: 控制台打印:家里有动静
傻瓜口令:"watch 盯整体,稍微动就报"
阶段11:传递盒子
目标:函数修改 reactive
操作:
// 导包
import { reactive } from 'vue'
// 操作代码
let ren = reactive({ age: 10 })
function guoShengRi(user) {
user.age = user.age + 1
}
guoShengRi(ren) // 把盒子传进去
模板(<template>)里写:
{{ ren.age }}
结果: 页面显示 11
傻瓜口令:"reactive 进函数,里外一起变"
阶段12:只能看不能改
目标:Readonly 保护数据
操作:
// 导包
import { reactive, readonly } from 'vue'
// 操作代码
let ren = reactive({ age: 10 })
// 套个壳
let onlyRen = readonly(ren)
onlyRen.age = 100 // 这一行会失败
模板(<template>)里写:
看控制台警告
结果: 控制台会有黄色警告:Set operation on key "age" failed: target is readonly.
傻瓜口令:"readonly 套个壳,只能看不能改"
阶段13:列表变变变
目标:reactive 驱动 v-for
操作:
// 导包
import { reactive } from 'vue'
// 操作代码
let caiDan = reactive(['红烧肉', '青菜'])
模板(<template>)里写:
<ul>
<li v-for="cai in caiDan">{{ cai }}</li>
</ul>
结果: 页面显示两行点点列表
傻瓜口令:"reactive 配循环,数据变它变"
阶段14:变色龙
目标:Style 绑定 reactive 对象
操作:
// 导包
import { reactive } from 'vue'
// 操作代码
let yangShi = reactive({ color: 'red', fontSize: '20px' })
function bianLan() {
yangShi.color = 'blue'
}
模板(<template>)里写:
<div :style="yangShi">我是一个字</div>
<button @click="bianLan">变蓝</button>
结果: 点击按钮,字的颜色变蓝
傻瓜口令:"样式装进 reactive,想变你就变"
阶段15:借尸还魂 (Object.assign)
目标:整体替换对象数据,且不断连
操作:
// 导包
import { reactive } from 'vue'
// 操作代码
let ren = reactive({ name: '强强', age: 10 })
// 模拟接口返回新数据
let newData = { name: '壮壮', age: 11 }
// 把新数据“倒进”旧盒子里
Object.assign(ren, newData)
模板(<template>)里写:
{{ ren }}
结果: 页面内容更新,且响应式不断
错误示例:
ren = newData
// 后果:这是赋值新变量,reactive 断连,页面卡死在旧数据
傻瓜口令:"整体换数据,assign 来拷贝"
阶段16:快速清空
目标:清空数组且不断连
操作:
// 导包
import { reactive } from 'vue'
// 操作代码
let paiDui = reactive(['猫', '狗'])
function clear() {
paiDui.length = 0 // 长度设为0,直接空
}
模板(<template>)里写:
{{ paiDui }}
<button @click="clear">清空</button>
结果: 数组变成 [],页面更新
错误示例:
paiDui = []
// 后果:断连
傻瓜口令:"数组清空,长度设为零"
阶段17:只管面子 (shallowReactive)
目标:只监听第一层变化
操作:
// 导包
import { shallowReactive } from 'vue'
// 操作代码
let ren = shallowReactive({
info: { city: '上海' }
})
// 改深层(不动)
function changeDeep() { ren.info.city = '北京' }
// 改表层(动)
function changeSurface() { ren.info = { city: '广州' } }
模板(<template>)里写:
{{ ren }}
<button @click="changeDeep">改深层(不动)</button>
<button @click="changeSurface">改表层(动)</button>
结果: 无论怎么点“改深层”都没反应,一点“改表层”立马更新
傻瓜口令:"只管第一层,深了看不见"
阶段18:现原形 (toRaw)
目标:获取原始对象(不响应)
操作:
// 导包
import { reactive, toRaw } from 'vue'
// 操作代码
let ren = reactive({ age: 10 })
let normalRen = toRaw(ren) // 变回普通对象
function test() {
normalRen.age++
console.log('改了,但页面不动', normalRen.age)
}
模板(<template>)里写:
{{ ren.age }}
<button @click="test">改原始数据</button>
结果: 控制台数字在变,页面数字死活不动
傻瓜口令:"toRaw 现原形,变回普通身"
阶段19:打封条 (markRaw)
目标:标记对象永远不响应
操作:
// 导包
import { reactive, markRaw } from 'vue'
// 操作代码
// 这是一个巨大的、不想被监听的数据
let bigData = markRaw({ list: '一万条数据' })
let ren = reactive({ data: bigData })
function change() {
ren.data.list = '改不动'
}
模板(<template>)里写:
{{ ren.data.list }}
<button @click="change">试图修改</button>
结果: 点击修改,页面不动(Vue 放弃了对它的控制,节省性能)
傻瓜口令:"markRaw 贴封条,永远不响应"
阶段20:查户口 (isReactive)
目标:检查变量是不是 reactive
操作:
// 导包
import { reactive, isReactive } from 'vue'
// 操作代码
let a = reactive({})
let b = {}
console.log('a是吗', isReactive(a))
console.log('b是吗', isReactive(b))
模板(<template>)里写:
看控制台
结果: a是吗 true,b是吗 false
傻瓜口令:"isReactive 查身份,看谁是响应"
阶段21:自动脱壳
目标:reactive 里放 ref 免 .value
操作:
// 导包
import { reactive, ref } from 'vue'
// 操作代码
let count = ref(10)
let state = reactive({ count }) // 把 ref 放进去
// 修改
state.count = 20 // 爽!不用写 .value
模板(<template>)里写:
{{ state.count }} | {{ count }}
结果: 两个都变成了 20
傻瓜口令:"ref 进对象,自动脱外壳"
阶段22:拆包就死
目标:演示解构赋值导致响应性丢失(新手最大坑)
操作:
// 导包
import { reactive } from 'vue'
// 操作代码
let ren = reactive({ age: 10 })
// 这一步把 age 拿出来,它就变成了普通的数字 10
let { age } = ren
function jia() {
ren.age++ // ren.age 变了
// 但是变量 age 还是 10,永远不会变
}
模板(<template>)里写:
<p>Proxy里的: {{ ren.age }}</p>
<p>拆出来的: {{ age }} (死数据)</p>
<button @click="jia">加一岁</button>
结果: Proxy里的在变,拆出来的死活不动
傻瓜口令:"直接拆对象,响应全断气"
阶段23:救兵 toRefs
目标:使用 toRefs 保持解构后的响应性
操作:
// 导包
import { reactive, toRefs } from 'vue'
// 操作代码
let ren = reactive({ age: 10, name: 'A' })
// toRefs 把每个属性都变成了 ref
let { age, name } = toRefs(ren)
function jia() {
age.value++ // 注意!拆出来变成 ref 了,要点 .value
}
模板(<template>)里写:
{{ age }}
<button @click="jia">这样就能动</button>
结果: 现在拆出来的变量也能联动了
傻瓜口令:"toRefs 转一转,拆开也有救"
阶段24:数组里的 Ref 很顽固
目标:注意数组里的 Ref 不会自动解包
操作:
// 导包
import { reactive, ref } from 'vue'
// 操作代码
let count = ref(100)
// 虽然放在 reactive 数组里
let list = reactive([count])
// 但是!取出来必须点 .value
// list[0] 是个 ref 对象
模板(<template>)里写:
错误写法: {{ list[0] }} (显示为 Object)
正确写法: {{ list[0].value }} (显示 100)
结果: 数组没有“自动脱壳”功能,必须手动点 value
傻瓜口令:"Ref 躲数组,外壳脱不掉"
阶段25:Ref 躲 Map
目标:Map 里的 Ref 也不解包(冷知识)
操作:
// 导包
import { reactive, ref } from 'vue'
// 操作代码
let map = reactive(new Map([['key', ref(50)]]))
模板(<template>)里写:
{{ map.get('key').value }}
结果: 和数组一样,必须手动点 value
傻瓜口令:"Map 藏 Ref,一样点 value"
阶段26:真假美猴王
目标:Proxy 不等于 Original
操作:
// 导包
import { reactive } from 'vue'
// 操作代码
let raw = {} // 真身
let proxy = reactive(raw) // 替身
// 它们不是同一个东西
console.log('是一样吗?', raw === proxy)
模板(<template>)里写:
看控制台
结果: 打印 false
傻瓜口令:"真身替身不相等,别搞混"
阶段27:多重分身
目标:重复 reactive 还是它
操作:
// 导包
import { reactive } from 'vue'
// 操作代码
let a = reactive({})
// 试图再包一层
let b = reactive(a)
console.log('是一样吗?', a === b)
模板(<template>)里写:
看控制台
结果: 打印 true。Vue 很聪明,不会重复包装。
傻瓜口令:"包了再包,还是它自己"
阶段28:禁止混用
目标:不要同时操作 raw 和 proxy
操作:
// 导包
import { reactive } from 'vue'
// 操作代码
let raw = { count: 0 }
let proxy = reactive(raw)
// ❌ 极其危险的操作:
// 一会改 proxy.count++
// 一会改 raw.count++
// 后果:逻辑混乱,可能触发多余更新或不更新
结论: 一旦生成了 proxy,就把 raw 忘掉!只操作 proxy。
傻瓜口令:"有了 reactive,忘掉原对象"
阶段29:Ref 和 Reactive 终极对比
目标:一句话治好选择困难症
| 场景 | 选谁? | 理由 |
|---|---|---|
| 数字、文字、布尔 | Ref | 没得选,只能 Ref |
| 对象、数组 | Reactive | 省得写 .value |
| 需要解构赋值 | Ref | Reactive拆开会死 |
| 团队协作 | 约定 | 听队长的 |
傻瓜口令:"数字用 Ref,对象随意选"