所有代码可直接复制到 Vue3 +
<script setup>环境中运行
阶段1:创建数字
目标:创建第一个 ref
操作:
// 导包
import { ref } from 'vue'
// 操作代码
let pingGuo = ref(5)
模板(<template>)里写:
{{ pingGuo }}
结果: 页面显示 5
错误示例:
let pingGuo = 5
// 后果:页面显示5,但永远不会动
傻瓜口令:"ref 装数字"
阶段2:修改数字
目标:修改 ref 的值
操作:
// 导包
import { ref } from 'vue'
// 操作代码
let pingGuo = ref(5)
pingGuo.value = 10
模板(<template>)里写:
{{ pingGuo }}
结果: 页面数字从 5 变成 10
错误示例:
pingGuo = 10
// 后果:页面还是5,不会动
傻瓜口令:"ref 改要 .value"
阶段3:再试一次
目标:巩固修改操作
操作:
// 导包
import { ref } from 'vue'
// 操作代码
let xiangJiao = ref(2)
xiangJiao.value = 8
模板(<template>)里写:
{{ xiangJiao }}
结果: 页面数字从 2 变成 8
错误示例:
xiangJiao = 8
// 后果:页面还是2,不会动
傻瓜口令:"ref 改要 .value"
阶段4:包装文字
目标:ref 也能装文字
操作:
// 导包
import { ref } from 'vue'
// 操作代码
let mingZi = ref('强强')
mingZi.value = '壮壮'
模板(<template>)里写:
{{ mingZi }}
结果: 页面文字从 强强 变成 壮壮
错误示例:
mingZi = '壮壮'
// 后果:页面还是强强,不会动
傻瓜口令:"ref 装文字,也能用"
阶段5:包装开关
目标:ref 也能装开关
操作:
// 导包
import { ref } from 'vue'
// 操作代码
let kaiGuan = ref(true)
kaiGuan.value = false
模板(<template>)里写:
{{ kaiGuan }}
结果: 页面显示 false
错误示例:
kaiGuan = false
// 后果:页面还是 true,不会动
傻瓜口令:"ref 装开关,也能用"
阶段6:包装对象
目标:ref 也能装盒子
操作:
// 导包
import { ref } from 'vue'
// 操作代码
let heZi = ref({ name: '苹果' })
heZi.value = { name: '香蕉' }
模板(<template>)里写:
{{ heZi }}
结果: 页面显示 { "name": "香蕉" }
错误示例:
heZi = { name: '香蕉' }
// 后果:页面还是苹果,不会动
傻瓜口令:"ref 装盒子,整体换"
阶段7:ref 包深层盒子
目标:修改盒子里的东西
操作:
// 导包
import { ref } from 'vue'
// 操作代码
let heZi = ref({ name: '苹果' })
heZi.value.name = '香蕉'
模板(<template>)里写:
{{ heZi.name }}
结果: 页面文字从 苹果 变成 香蕉
错误示例:
heZi.name = '香蕉'
// 后果:忘写 .value,报错或者不更新
傻瓜口令:"ref 包盒子,里面也能改"
阶段8:ref 列表加项
目标:往列表里加东西
操作:
// 导包
import { ref } from 'vue'
// 操作代码
let paiDui = ref(['猫', '狗'])
paiDui.value.push('鸟')
模板(<template>)里写:
{{ paiDui }}
结果: 页面显示 [ "猫", "狗", "鸟" ]
错误示例:
paiDui.push('鸟')
// 后果:忘写 .value,报错
傻瓜口令:"ref 包列表,加项用 push"
阶段9:ref 列表删项
目标:删掉最后那个
操作:
// 导包
import { ref } from 'vue'
// 操作代码
let paiDui = ref(['猫', '狗'])
paiDui.value.pop()
模板(<template>)里写:
{{ paiDui }}
结果: 页面只剩下 [ "猫" ]
错误示例:
paiDui.pop()
// 后果:忘写 .value,报错
傻瓜口令:"ref 包列表,删项用 pop"
阶段10:让 ref 自动算
目标:自动算出结果
操作:
// 导包
import { ref, computed } from 'vue'
// 操作代码
let shuZi = ref(2)
let chengGuo = computed(() => shuZi.value + 2)
模板(<template>)里写:
{{ chengGuo }}
结果: 页面显示 4
错误示例:
let chengGuo = computed(() => shuZi + 2)
// 后果:计算里忘写 .value,结果不对
傻瓜口令:"computed 帮 ref 自动算"
阶段11:盯着 ref 变
目标:数据变了打日志
操作:
// 导包
import { ref, watch } from 'vue'
// 操作代码
let shuZi = ref(0)
watch(shuZi, () => console.log('变了'))
shuZi.value = 1
模板(<template>)里写:
{{ shuZi }}
结果: 打开控制台(F12),看到输出 "变了"
错误示例:
watch(shuZi.value, ...)
// 后果:watch 只需要名字,不要 .value
傻瓜口令:"watch 盯着 ref,变了就报告"
阶段12:ref 进函数
目标:把 ref 传给函数
操作:
// 导包
import { ref } from 'vue'
// 操作代码
function jiaYi(n) {
n.value = n.value + 1
}
let shuZi = ref(0)
jiaYi(shuZi)
模板(<template>)里写:
{{ shuZi }}
结果: 页面显示 1
错误示例:
function jiaYi(n) { n = n + 1 }
// 后果:没有改 .value,外面不会变
傻瓜口令:"ref 进函数,里面也能改"
阶段13:ref 给标签起名
目标:给 input 起名字
操作:
// 导包
import { ref } from 'vue'
// 操作代码
let shuRuKuang = ref(null)
模板(<template>)里写:
<input ref="shuRuKuang" value="点点我" />
结果: 页面显示一个输入框
错误示例:
let abc = ref(null)
// <input ref="shuRuKuang" />
// 后果:名字对不上,抓不到
傻瓜口令:"ref 给标签,起个好名字"
阶段14:操作抓到的标签
目标:让输入框自动聚焦
操作:
// 导包
import { ref, onMounted } from 'vue'
// 操作代码
let shuRuKuang = ref(null)
onMounted(() => {
shuRuKuang.value.focus()
})
模板(<template>)里写:
<input ref="shuRuKuang" />
结果: 页面刷新后,输入框已经处于打字状态(有光标)
错误示例:
shuRuKuang.focus()
// 后果:忘写 .value,报错
傻瓜口令:"ref 抓标签,点点能聚焦"
阶段15:ref 对战 reactive
目标:对比两者的区别
对比表格:
| 场景 | ref | reactive |
|---|---|---|
| 包装内容 | 万能(数字/对象) | 只能是对象 |
| 访问方式 | 必须 .value | 直接访问 |
| 替换整体 | 可以 | 不可以 |
操作:
// 导包
import { ref, reactive } from 'vue'
// 操作代码
let refNum = ref(10)
let reactiveObj = reactive({ n: 20 })
// 修改
refNum.value = 11
reactiveObj.n = 21
模板(<template>)里写:
{{ refNum }} - {{ reactiveObj.n }}
结果: 页面显示 11 - 21
傻瓜口令:"ref 啥都能装,reactive 装对象"
阶段16:牵一根线 (toRef)
目标:把对象属性变 ref
操作:
// 导包
import { reactive, toRef } from 'vue'
// 操作代码
let ren = reactive({ age: 10 })
let ageRef = toRef(ren, 'age')
ageRef.value = 11 // 改这个
模板(<template>)里写:
{{ ren.age }}
结果: ren.age 也变成了 11,说明连着线
错误示例:
let age = ren.age // 这样只是复制数字
age = 12 // ren.age 不会变
傻瓜口令:"toRef 牵根线,两头一起变"
阶段17:全部拆开 (toRefs)
目标:解构对象不丢响应
操作:
// 导包
import { reactive, toRefs } from 'vue'
// 操作代码
let ren = reactive({ name: '强强', age: 10 })
// 拆开变成两个 ref
let { name, age } = toRefs(ren)
age.value = 20
模板(<template>)里写:
{{ ren.age }}
结果: ren.age 也变成 20,说明都连着线
错误示例:
let { age } = ren // 这样拆开就断了
age = 20 // ren.age 不会变
傻瓜口令:"toRefs 全拆开,还是连着线"
阶段18:查身份 (isRef)
目标:检查是否是 ref
操作:
// 导包
import { ref, isRef } from 'vue'
// 操作代码
let a = ref(1)
let b = 1
console.log('a是吗?', isRef(a))
console.log('b是吗?', isRef(b))
模板(<template>)里写:
看控制台
结果: 控制台打印:a是吗?true,b是吗?false
傻瓜口令:"isRef 查身份,看它是谁"
阶段19:只管外壳 (shallowRef)
目标:只监听外层变化
操作:
// 导包
import { shallowRef } from 'vue'
// 操作代码
let s = shallowRef({ n: 1 })
// 动作1:改里面(无效)
function changeInner() { s.value.n = 2 }
// 动作2:改外壳(有效)
function changeOuter() { s.value = { n: 100 } }
模板(<template>)里写:
{{ s }}
<button @click="changeInner">改里面</button>
<button @click="changeOuter">改外壳</button>
结果: 点“改里面”页面不动;点“改外壳”页面变100
傻瓜口令:"shallowRef 这一点,只管最外层"
阶段20:强制更新 (triggerRef)
目标:强制 shallowRef 更新
操作:
// 导包
import { shallowRef, triggerRef } from 'vue'
// 操作代码
let s = shallowRef({ n: 1 })
function forceChange() {
s.value.n = 50
triggerRef(s) // 踢一脚
}
模板(<template>)里写:
{{ s }}
<button @click="forceChange">强制改</button>
结果: 点击后页面变成 50
傻瓜口令:"triggerRef 踢一脚,强制刷新它"
阶段21:L2 总结
目标:总结什么时候用什么
对比表格:
| 只是想存个数据 | 用 ref |
|---|---|
| 想存一堆且不用替换 | 用 reactive |
| 想把 reactive 拆开 | 用 toRefs |
| 数据巨大只需换整体 | 用 shallowRef |
操作: (无新增代码,复习上述概念)
傻瓜口令:"基本用 ref,对象看情况"
阶段22:值拿出来就死
目标:理解赋值断连
错误场景:把 value 拿出来给别的变量
错误代码:
// 错误代码
import { ref } from 'vue'
let a = ref(10)
let b = a.value // b 只是个数字 10
b = 20
错误后果: a 还是 10,根本没变
正确代码:
// 正确代码
import { ref } from 'vue'
let a = ref(10)
a.value = 20 // 必须改 .value
解决方法: 永远操作源头 ref,别操作拿出来的临时变量
傻瓜口令:"值拿出来,就断了线"
阶段23:ref 进对象自动脱
目标:对象里不用写 .value
错误场景:在 reactive 对象里多写了 .value
错误代码:
// 错误代码
import { ref, reactive } from 'vue'
let r = ref(10)
let obj = reactive({ r })
obj.r.value = 20 // 错!不需要 .value
错误后果: obj.r.value 是 undefined,报错
正确代码:
// 正确代码
obj.r = 20 // 自动就把 r 改了
解决方法: 记住:ref 只要进到 reactive 对象里,外壳就自动脱了
傻瓜口令:"ref 进对象,外壳自动脱"
阶段24:ref 进数组不脱
目标:数组里必须写 .value
错误场景:在数组里忘了写 .value
错误代码:
// 错误代码
import { ref, reactive } from 'vue'
let r = ref(10)
let arr = reactive([r])
arr[0] = 20 // 错!这把 ref 覆盖了
错误后果: r 还是 10,arr[0] 变成了普通数字 20,断连了
正确代码:
// 正确代码
arr[0].value = 20 // 必须加 .value
解决方法: 记住:数组特殊,ref 进去不脱壳
傻瓜口令:"ref 进数组,外壳还得留"
阶段25:watch 改自己
目标:避免死循环
错误场景:监听变化时又去改它,无限循环
错误代码:
// 错误代码
import { ref, watch } from 'vue'
let n = ref(0)
watch(n, () => {
n.value++ // 无限+1,卡死
})
n.value = 1
错误后果: 浏览器卡死,页面崩溃
正确代码:
// 正确代码
watch(n, () => {
if(n.value < 5) n.value++ // 加个刹车
})
解决方法: 只要在 watch 里改自己,必须加 if 判断停止条件
傻瓜口令:"watch 改自己,必须加条件"
阶段26:直接打印一大坨
目标:调试看 .value
错误场景:console.log(ref) 看不到值
错误代码:
// 错误代码
import { ref } from 'vue'
let n = ref(10)
console.log(n)
错误后果:
控制台显示 RefImpl 一大堆属性,找不到 10 在哪
正确代码:
// 正确代码
console.log(n.value) // 显示 10
解决方法: 调试打印记得加 .value
傻瓜口令:"直接看 ref,是一大坨"
阶段27:人走茶凉
目标:组件销毁清定时器
错误场景:组件关了,定时器还在跑
错误代码:
// 错误代码
import { onMounted } from 'vue'
onMounted(() => {
setInterval(() => console.log('跑'), 1000)
})
// 没写 onUnmounted
错误后果: 组件关了,后台还在一直打印,这叫内存泄漏
正确代码:
// 正确代码
import { onUnmounted } from 'vue'
let t = null
onMounted(() => t = setInterval(...))
onUnmounted(() => clearInterval(t))
解决方法: 有始有终,一定要清理
傻瓜口令:"人走茶凉,别再改数"
阶段28:名字起一样
目标:变量名别和 ref 重名
错误场景:数据变量和标签 ref 重名
错误代码:
// 错误代码
import { ref } from 'vue'
let count = ref(10)
Template: <div ref="count">
错误后果: count 变成了那个 div 标签,10 丢了
正确代码:
// 正确代码
let countData = ref(10)
// <div ref="countEl">
解决方法: 数据叫 xxData,标签叫 xxEl,区分开
傻瓜口令:"名字起一样,数字变标签"
阶段29:L3 总结
目标:Ref 避坑指南
总结:
- 拿出来就废:不要把 .value 赋值给别人用
- 进出有别:进对象脱,进数组不脱
- 名字别撞:Variable 和 DOM Ref 别重名
- 遇事不决:先检查是不是忘了 .value
傻瓜口令:"遇事不决,先查 .value"