2.2 reactive 让对象变成"活的"

12 阅读9分钟

阶段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
需要解构赋值RefReactive拆开会死
团队协作约定听队长的

傻瓜口令:"数字用 Ref,对象随意选"