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\ emit
-
attrs: 包含没有在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-types
setup() {
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 = likes
state.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: number
return 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
方法创建的代理