Vue3 响应式难哭?这份笔记把 ref/reactive 讲得比奶茶还甜
目录
1 基础三剑客
1 找到index.html
需要将main.ts引入
<script type="module" src="/src/main.ts"></script>
2 main.ts
Vue3 最基础的 main.ts(导入根组件App.vue,实例创建,挂载)
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
3 App.vue
<template>
</template>
<script setup lang="ts" name="App">
</script>
<style scoped>
</style>
2 入门必看
1 ref/setup/reactive
ref:让数据变为响应式
setup:一个十分方便的语法糖,可以将数据方法逻辑都定义再script的脚本当中。
reactive:主要是包裹对象/数组。
代码实现:
我们现在实现一个交互:有一个响应式的数据为10,点击之后数据乘以2.
定义组件:(在src/components/Demo.vue)(CSS样式就不展示了)
<template>
<div>
<span>{{ num }}</span>
<button @click="f1" key="">点击测试</button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const num = ref(10)
const f1 = () => {
num.value *= 2
}
</script>
将其添加到根组件App.vue当中
<template>
<demo />
</template>
<script setup lang="ts" name="App">
import Demo from '@/components/Demo.vue'
</script>
<style scoped></style>
2 深入理解ref与reactive
当我们使用reactive就会创建一个Proxy对象,Vue的响应式系统就会监控这个对象的地址。
(1 当我们去对reactive包裹的对象进行直接解构,这样就会失去响应式
只有通过代理去进行操作内部的值才会被监控到,当我们执行解构时相当于只执行了一次
const name = user.name const age = user.age
这里的初次是通过了代理,是可以被监控到的但是后续就是直接获取对应的值了就不再通过代理了
const user = reactive({ name: '张三', age: 18 })
const { name, age } = user
(2 当我们对reactive包裹的对象进行重新赋值,也会失去响应式
相当于监控的是0x100但是现在重新赋值,user变量现在指向了0x200,已经脱离了监控。
let user = reactive({ name: '张三', age: 18 })
user = { name: '李四', age: 20 }
(3 如何使用呢
可以借助torefs进行解构,也可以每次都严格借助代理调用
可以使用Object.assign()进行对象赋值
import { reactive, toRefs } from 'vue';
const user = reactive({ name: '张三', age: 18 });
const { name, age } = toRefs(user);
console.log(name.value);
console.log(age.value);
import { reactive } from 'vue';
const user = reactive({ name: '张三', age: 18 });
console.log(user.name);
console.log(user.age);
(4 ref
核心出装:.value
原理:js语言存在一个问题,就是变量之间的值传递都是数值拷贝,而并非引用传递,对此vue创建或者说发明了一个RefImpl对象(Ref盒子),借助.value调用其真实的值,vue监控的值就是这个.value值,通过拦截.value的读取与修改,实现快速监控。
-
ref会返回一个 RefImpl 对象(一个“盒子”) -
实际的值被放在这个对象的
.value中 -
Vue 通过 劫持
.value的 getter / setter 来实现依赖收集与触发更新 -
如果
.value是对象,该对象会再被reactive包装成 Proxy
const count = ref(10) // count 指向盒子 0x300
count.value = 20 // 我们没有改 count 的指向!
3 computed
computed:带缓存的计算,当原有的数据不变化时这个方法就不会重复计算(一个计算属性)。
conputed代码示例:(CSS样式省略)
conputed是一个计算属性,其调用的原理就是当依赖的值发生变化时,才会重新计算,这个依赖的值就是computed当中引用的值发生了变化Vue就会重新计算这个computed属性。
<template>
<div>
<span>原始值:{{ num }}</span>
<span>计算值:{{ doubleNum }}</span>
<button @click="f1">点击测试</button>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
const num = ref(10)
const doubleNum = computed(() => {
console.log('computed 重新计算了')
return num.value * 2
})
const f1 = () => {
num.value++
}
</script>
4 watch
watch:当某个数据发生变化时,自动触发一个后续操作。
watch代码示例:
分别有四种情况
- 监听基础类型:没有什么注意的,就是旧值改变就执行当前代码逻辑。
- 监听ref的对象当中的某个属性:这种是比较常见的监听模式
- 监听ref修饰的对象,会出现旧值与新值不同的情况,因为监视的是.value对象的引用地址,如果说只是修改内部的一些属性,会读取对象地址,在不改变对象引用地址的前提下,方法不会被调用(ref默认是浅监视),但是我是手动开启深度监视(透过外层的Refimpl盒子去看Proxy),此时修改了对象下的属性值也会被察觉到,但是监视记录的实际值还是对象的地址值,地址值是原先的,调用时这个地址值下的值已经被修改了,进而调用时都是新值。
- 监听reactive修饰的对象:直接袒露的就是代理的Proxy对象。同样受限于引用传递,新旧值指向同一对象。
<template>
<div>
<span>原始值:{{ num }}</span>
<span>监听后值:{{ num1 }}</span>
<button @click="f1">点击测试</button>
</div>
</template>
<script setup lang="ts">
import { ref, watch, reactive } from 'vue'
const num = ref(10)
const num1 = ref(11)
const num2 = ref({ name: 'ax', age: 18 })
const num3 = reactive({ name: 'ax', age: 18 })
// 监听基础类型
watch(num, (newVal, oldVal) => {
console.log('watch 触发了', oldVal, '->', newVal)
num1.value = newVal * 2
})
// 监听对象属性
watch(
() => num2.value.age,
(newVal, oldVal) => {
console.log('年龄修改了:', oldVal, '->', newVal)
},
)
// 监听整个对象(ref)
watch(
num2,
(newVal, oldVal) => {
console.log('对象修改了', newVal, oldVal)
},
{ deep: true },
)
//监视对象(reactive)
watch(num3, (newVal, oldVal) => {
console.log('对象修改了', newVal, oldVal)
})
const f1 = () => {
num.value++
num2.value.age++
num3.age++
}
</script>
图片展示:

核心区别:一句话心法(gemini生成)
想要“新值”?用
computed。想要“办事”?用
watch。
computed: “依赖 A,算出一个 B。”
它有缓存。只有 A 变了,它才会重新算,否则直接给你上次算好的结果。
它必须有返回值 (
return)。绝对不要在里面干坏事(发请求、改 DOM、设定时器)。它应该是纯洁的计算逻辑。
watch: “当 A 变了,去干一件事(X)。”
它没有缓存。每次 A 变了,它就跑一次。
它不需要返回值。
它是专门用来干坏事的(发请求、打印日志、操作 DOM、存 LocalStorage)。
5 hooks
基础概念:hooks就是将对应的功能代码(方法逻辑/变量/方法)将其单独定义在一个ts文件当中,如果说需要使用的话,直接对应的导入即可,就不需要全部放在组件的内部去进行声明调用,避免出现三个或者多个组件共用一段代码片段的情况。
当你发现两个组件写了类似的代码时,就是你创建 Hooks 的最佳时机。
<template>
<div class="card">
<h2>这是一个Hooks 测试组件</h2>
<p>当前数值:{{ sum }}</p>
<p>放大十倍:{{ bigSum }}</p>
<button @click="add">点我 +1</button>
</div>
</template>
<script setup lang="ts">
// 1. 导入刚才定义的 Hook
import useSum from '@/hooks/useSum'
// 2. 调用 Hook,并解构出我们需要的数据和方法
// 就像买了个工具包,把里面的 锤子(sum)、扳手(add) 拿出来用
const { sum, add, bigSum } = useSum()
</script>
// 1. 引入 Vue 需要的 API
import { ref, computed, onMounted } from 'vue'
// 2. 导出组合式函数 (命名规范:useXxx)
export default function useSum() {
// --- A. 定义数据 (State) ---
const sum = ref(0)
// --- B. 定义方法 (Actions) ---
const add = () => {
sum.value += 1
}
// --- C. 定义计算属性 (Computed) ---
// Hooks 里面也可以包含计算属性!
const bigSum = computed(() => {
return sum.value * 10
})
// --- D. 定义生命周期 (Lifecycle) ---
// 只要组件用了这个 Hook,组件挂载时就会自动触发这里
onMounted(() => {
console.log('useSum Hook 里的 onMounted 被触发了!')
})
// --- E. 把组件需要的东西交出去 (Return) ---
return {
sum,
add,
bigSum,
}
}
更多推荐
所有评论(0)