ref
作用: 定义一个数据的响应式 (响应式数据:数据变化,页面跟着渲染变化)
- 一般用来定义一个基本类型的响应式数据
ref是一个函数,作用:定义一个响应式的数据,返回的是一个Ref对象,对象中有一个value属性,如果需要对数据进行操作,需要使用该Ref对象调用value属性的方式进行数据的操作。
但是在htm模版中是不需要.value属性的
setup() {let x = ref(0);const counter = () => {x.value += 1;console.log(x.value);};return { x, counter };},
reactive
- 作用: 定义多个数据的响应式
- const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象
- 响应式转换是“深层的”:会影响对象内部所有嵌套的属性
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
它返回的是一个Proxy的代理对象,被代理的目标对象就是 reactive() 括号里面声明的对象。
下面的代码被代理的对象就是 obj。user是代理对象,obj是目标对象。
直接使用目标对象的方式来更新目标对象中的成员的值是不可能的,只能使用代理对象的方式来更新数据(响应式数据)
<script lang="ts">import { defineComponent, reactive } from "vue";export default defineComponent({name: "Home",setup() {const obj = {name: "ade kang",age: 20,wife: {name: "tom",age: 18,cars: ["奔驰", "宝马"],},};const user = reactive(obj);console.log(user);return { user };},});</script>
上面代码运行打印出来的是,可以看出目标对象就是obj里面的一些属性

对于代理对象和目标对象里面增加或者删除对象里面的数据,都会更新界面数据。
user.name = "jack";obj.gender = "男";delete obj.age;user.gender = "女";
上面的代码都会更新页面的数据。
Vue3响应式理解
- 通过Proxy(代理): 拦截对data任意属性的任意(13种)操作, 包括属性值的读写, 属性的添加, 属性的删除等… 可查看MDN文档:Proxy
- 通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作 可查看MDN文档:Reflect
<script>const user = {name: "ade kang",age: 20,wife: {name: "tom",age: 18,cars: ["奔驰", "宝马"],},};const proxyUser = new Proxy(user, {// 获取目标属性值get(target, prop) {console.log("get方法调用了");return Reflect.get(target, prop);},// 修改/更新目标对象属性值set(target, prop, val) {console.log("set方法调用了");return Reflect.set(target, prop, val);},deleteProperty(target, prop) {console.log("delete方法调用了");return Reflect.deleteProperty(target, prop);},});// 通过代理对象获取目标对象中的某个属性值console.log(proxyUser.name);// 通过代理对象更新目标对象上的某个属性值proxyUser.name = "jack long";console.log(user);// 通过代理对象向目标对象中添加一个新的属性proxyUser.gender = "男";console.log(user);// 删除对象delete proxyUser.name;console.log(user);</script>
下面是输出的信息

setup的细节
setup的执行时机
在beforeCreate之前执行(一次), 此时组件对象还没有创建
this是undefined, 不能通过this来访问data/computed/methods / props
其实所有的composition API相关回调函数中也都不可以
<script lang="ts">import { defineComponent } from "vue";export default defineComponent({name: "child",props: {msg: String,},beforeCreate() {console.log("beforeCreate执行了");},setup() {console.log("setup 执行了");},});</script>
我们创建一个子组件,然后调用会除下以下现象

如若再setup里卖弄使用this会直接报错
setup返回值
setup中的返回值是—个对象,内部的属性和方法是给html模版使用的
setup中的对象内部的属性和data函数中的return对象的属性都可以在html模版中使用
setup中的对象中的属性和data函数中的对象中的属性会合并为组件对象的属性
setup中的对象中的方法和methods对象中的方法会合并为组件对象的方法
如果有重名, setup优先
注意:
一般不要混合使用: methods中可以访问setup提供的属性和方法, 但在setup方法中不能访问data和methods
setup不能是一个async函数: 因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性数据
setup的参数
setup(props, context) / setup(props, {attrs, slots, emit})
props参数,是一个对象,里面有父级组件向子级组件传递的数据,并且是在子级组件中使用props接收到的所有的属性
context接受的参数有attrs\slots\ emitattrs: 包含没有在props配置中声明的属性的对象, 相当于 this.$attrs
slots: 包含所有传入的插槽内容的对象, 相当于 this.$slots
emit: 用来分发自定义事件的函数, 相当于 this.$emit
setup(props, context) {console.log("========");console.log(props);console.log("-------");console.log(context);},
我只直接打印出 setup 中的 props和context。这样就很清楚的发现有什么了。

reactive与ref-细节
- 是Vue3的 composition API中2个最重要的响应式API
- ref用来处理基本类型数据, reactive用来处理对象(递归深度响应式)
- 如果用ref对象/数组, 内部会自动将对象/数组转换为reactive的代理对象
- ref内部: 通过给value属性添加getter/setter来实现对数据的劫持
- reactive内部: 通过使用Proxy来实现对对象内部所有数据的劫持, 并通过Reflect操作对象内部数据
- ref的数据操作: 在js中要.value, 在模板中不需要(内部解析模板时会自动添加.value)
计算属性与监视
computed函数
如果计算属性上面只有一个函数,那么它表示的是getter,同时返回的是一个 Ref类型的对象。
setup() {const user = reactive({firstName: "ade",lastName: "kang",});// 通过计算属性的方式,实现第一个姓名的显示const fullName1 = computed(() => {return user.firstName + "_" + user.lastName;});console.log(fullName1);return { user, fullName1 };},

同时也支持 setter。如果getter和setter一起传入 那么写法就是一个对象
const fullName2 = computed({get() {return user.firstName + "_" + user.lastName;},set(val) {console.log(val);const names = val.split("_");user.firstName = names[0];user.lastName = names[1];},});
watch函数
与watch配置功能一致
监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次
通过配置deep为true, 来指定深度监视
当watch里面监视非响应式数据时,给数据加上一个回调
watch([()=>user.firstName,()=>user.lastName,fullName],()=>{})
watchEffect函数
不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
默认初始时就会执行第一次, 从而可以收集需要监视的数据
监视数据发生变化时回调
toRefs
把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref。
如果要在setup中获取值就需要 state.name.value
<script lang="ts">import { defineComponent, reactive, toRefs } from "vue";export default defineComponent({name: "App",setup() {const state = reactive({name: "adekang",age: "18",});// const state2 = toRefs(state);const { name, age } = toRefs(state);setInterval(() => {// state.name += "-";name.value += "=";}, 1000);// return { state };// 不是响应式的return { name, age };},});</script>
ref获取元素
利用ref函数获取组件中的标签元素
功能需求: 让输入框自动获取焦点
<template><h2>App</h2><input type="text">---<input type="text" ref="inputRef"></template><script lang="ts">import { onMounted, ref } from 'vue'export default {setup() {const inputRef = ref<HTMLElement|null>(null)onMounted(() => {inputRef.value && inputRef.value.focus()})return {inputRef}},}</script>
Composition API(其它部分)
shallowReactive 与 shallowRef
- shallowReactive : 只处理了对象内最外层属性的响应式(也就是浅响应式)
- shallowRef: 只处理了value的响应式, 不进行对象的reactive处理
什么时候用浅响应式呢?
- 一般情况下使用ref和reactive即可
- 如果有一个对象数据, 结构比较深, 但变化时只是外层属性变化 ===> shallowReactive
- 如果有一个对象数据, 后面会产生新的对象来替换 ===> shallowRef
<template><h2>App</h2><h3>m1: {{ m1 }}</h3><h3>m2: {{ m2 }}</h3><h3>m3: {{ m3 }}</h3><h3>m4: {{ m4 }}</h3><button @click="update">更新</button></template><script lang="ts">import { reactive, ref, shallowReactive, shallowRef } from "vue";export default {// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-typessetup() {const m1 = reactive({ a: 1, b: { c: 2 } });const m2 = shallowReactive({ a: 1, b: { c: 2 } });const m3 = ref({ a: 1, b: { c: 2 } });const m4 = shallowRef({ a: 1, b: { c: 2 } });const update = () => {// 此处每个数据单独操作// m1.b.c += 1;m2.b.c += 1;// m3.value.a += 1;// m4.value.a += 1;};return {m1,m2,m3,m4,update,};},};</script>
readonly 与 shallowReadonly
readonly:
- 深度只读数据
- 获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。
- 只读代理是深层的:访问的任何嵌套 property 也是只读的。
shallowReadonly
- 浅只读数据
- 创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换
应用场景:
- 在某些特定情况下, 我们可能不希望对数据进行更新的操作, 那就可以包装生成一个只读代理对象来读取数据, 而不能修改或删除
toRaw 与 markRaw
toRaw
- 返回由
reactive或readonly方法转换成响应式代理的普通对象。 - 这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发界面更新。
- 返回由
markRaw
- 标记一个对象,使其永远不会转换为代理。返回对象本身
应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类实例或 Vue 组件对象。
- 当渲染具有不可变数据源的大列表时,跳过代理转换可以提高性能。
<template><h2>{{state}}</h2><button @click="testToRaw">测试toRaw</button><button @click="testMarkRaw">测试markRaw</button></template><script lang="ts">import {markRaw,reactive, toRaw,} from 'vue'export default {setup () {const state = reactive<any>({name: 'tom',age: 25,})const testToRaw = () => {const user = toRaw(state)user.age++ // 界面不会更新}const testMarkRaw = () => {const likes = ['a', 'b']// state.likes = likesstate.likes = markRaw(likes) // likes数组就不再是响应式的了setTimeout(() => {state.likes[0] += '--'}, 1000)}return {state,testToRaw,testMarkRaw,}}}</script>
toRef
为原响应式对象上的某个属性创建一个 ref对象, 二者内部操作的是同一个数据值, 更新时二者是同步的
区别ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响
应用: 当要将 某个prop 的 ref 传递给复合函数时,toRef 很有用
customRef
- 创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制
- 需求: 使用 customRef 实现 debounce 的示例
<template><h2>App</h2><input v-model="keyword" placeholder="搜索关键字"/><p>{{keyword}}</p></template><script lang="ts">/*需求:使用 customRef 实现 debounce 的示例*/import {ref,customRef} from 'vue'export default {setup () {const keyword = useDebouncedRef('', 500)console.log(keyword)return {keyword}},}/*实现函数防抖的自定义ref*/function useDebouncedRef<T>(value: T, delay = 200) {let timeout: numberreturn customRef((track, trigger) => {return {get() {// 告诉Vue追踪数据track()return value},set(newValue: T) {clearTimeout(timeout)timeout = setTimeout(() => {value = newValue// 告诉Vue去触发界面更新trigger()}, delay)}}})}</script>
provide 与 inject
- 实现跨层级组件(祖孙)间通信
<template><h1>父组件</h1><p>当前颜色: {{color}}</p><button @click="color='red'">红</button><button @click="color='yellow'">黄</button><button @click="color='blue'">蓝</button><hr><Son /></template><script lang="ts">import { provide, ref } from 'vue'import Son from './Son.vue'export default {name: 'ProvideInject',components: {Son},setup() {const color = ref('red')provide('color', color)return {color}}}</script>
<template><div><h2>子组件</h2><hr><GrandSon /></div></template><script lang="ts">import GrandSon from './GrandSon.vue'export default {components: {GrandSon},}</script>
<template><h3 :style="{color}">孙子组件: {{color}}</h3></template><script lang="ts">import { inject } from 'vue'export default {setup() {const color = inject('color')return {color}}}</script>
响应式数据的判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive创建的响应式代理 - isReadonly: 检查一个对象是否是由
readonly创建的只读代理 - isProxy: 检查一个对象是否是由
reactive或者readonly方法创建的代理
