ref

作用: 定义一个数据的响应式 (响应式数据:数据变化,页面跟着渲染变化)

  • 一般用来定义一个基本类型的响应式数据

ref是一个函数,作用:定义一个响应式的数据,返回的是一个Ref对象,对象中有一个value属性,如果需要对数据进行操作,需要使用该Ref对象调用value属性的方式进行数据的操作。

但是在htm模版中是不需要.value属性的

  1. setup() {
  2. let x = ref(0);
  3. const counter = () => {
  4. x.value += 1;
  5. console.log(x.value);
  6. };
  7. return { x, counter };
  8. },

reactive

  • 作用: 定义多个数据的响应式
  • const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象
  • 响应式转换是“深层的”:会影响对象内部所有嵌套的属性
  • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的

它返回的是一个Proxy的代理对象,被代理的目标对象就是 reactive() 括号里面声明的对象。

下面的代码被代理的对象就是 obj。user是代理对象,obj是目标对象。

直接使用目标对象的方式来更新目标对象中的成员的值是不可能的,只能使用代理对象的方式来更新数据(响应式数据)

  1. <script lang="ts">
  2. import { defineComponent, reactive } from "vue";
  3. export default defineComponent({
  4. name: "Home",
  5. setup() {
  6. const obj = {
  7. name: "ade kang",
  8. age: 20,
  9. wife: {
  10. name: "tom",
  11. age: 18,
  12. cars: ["奔驰", "宝马"],
  13. },
  14. };
  15. const user = reactive(obj);
  16. console.log(user);
  17. return { user };
  18. },
  19. });
  20. </script>

上面代码运行打印出来的是,可以看出目标对象就是obj里面的一些属性

51Vue 3 Composition API浅析 - 图1

对于代理对象和目标对象里面增加或者删除对象里面的数据,都会更新界面数据。

  1. user.name = "jack";
  2. obj.gender = "男";
  3. delete obj.age;
  4. user.gender = "女";

上面的代码都会更新页面的数据。

Vue3响应式理解

  • 通过Proxy(代理): 拦截对data任意属性的任意(13种)操作, 包括属性值的读写, 属性的添加, 属性的删除等… 可查看MDN文档:Proxy
  • 通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作 可查看MDN文档:Reflect
  1. <script>
  2. const user = {
  3. name: "ade kang",
  4. age: 20,
  5. wife: {
  6. name: "tom",
  7. age: 18,
  8. cars: ["奔驰", "宝马"],
  9. },
  10. };
  11. const proxyUser = new Proxy(user, {
  12. // 获取目标属性值
  13. get(target, prop) {
  14. console.log("get方法调用了");
  15. return Reflect.get(target, prop);
  16. },
  17. // 修改/更新目标对象属性值
  18. set(target, prop, val) {
  19. console.log("set方法调用了");
  20. return Reflect.set(target, prop, val);
  21. },
  22. deleteProperty(target, prop) {
  23. console.log("delete方法调用了");
  24. return Reflect.deleteProperty(target, prop);
  25. },
  26. });
  27. // 通过代理对象获取目标对象中的某个属性值
  28. console.log(proxyUser.name);
  29. // 通过代理对象更新目标对象上的某个属性值
  30. proxyUser.name = "jack long";
  31. console.log(user);
  32. // 通过代理对象向目标对象中添加一个新的属性
  33. proxyUser.gender = "男";
  34. console.log(user);
  35. // 删除对象
  36. delete proxyUser.name;
  37. console.log(user);
  38. </script>

下面是输出的信息

51Vue 3 Composition API浅析 - 图2

setup的细节

setup的执行时机

  • 在beforeCreate之前执行(一次), 此时组件对象还没有创建

  • this是undefined, 不能通过this来访问data/computed/methods / props

  • 其实所有的composition API相关回调函数中也都不可以

  1. <script lang="ts">
  2. import { defineComponent } from "vue";
  3. export default defineComponent({
  4. name: "child",
  5. props: {
  6. msg: String,
  7. },
  8. beforeCreate() {
  9. console.log("beforeCreate执行了");
  10. },
  11. setup() {
  12. console.log("setup 执行了");
  13. },
  14. });
  15. </script>

我们创建一个子组件,然后调用会除下以下现象

51Vue 3 Composition API浅析 - 图3

如若再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

  1. setup(props, context) {
  2. console.log("========");
  3. console.log(props);
  4. console.log("-------");
  5. console.log(context);
  6. },

我只直接打印出 setup 中的 propscontext。这样就很清楚的发现有什么了。

51Vue 3 Composition API浅析 - 图4

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类型的对象。

  1. setup() {
  2. const user = reactive({
  3. firstName: "ade",
  4. lastName: "kang",
  5. });
  6. // 通过计算属性的方式,实现第一个姓名的显示
  7. const fullName1 = computed(() => {
  8. return user.firstName + "_" + user.lastName;
  9. });
  10. console.log(fullName1);
  11. return { user, fullName1 };
  12. },

51Vue 3 Composition API浅析 - 图5

同时也支持 setter。如果getter和setter一起传入 那么写法就是一个对象

  1. const fullName2 = computed({
  2. get() {
  3. return user.firstName + "_" + user.lastName;
  4. },
  5. set(val) {
  6. console.log(val);
  7. const names = val.split("_");
  8. user.firstName = names[0];
  9. user.lastName = names[1];
  10. },
  11. });

watch函数

  • 与watch配置功能一致

  • 监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调

  • 默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次

  • 通过配置deep为true, 来指定深度监视

  • 当watch里面监视非响应式数据时,给数据加上一个回调

    1. watch([()=>user.firstName,()=>user.lastName,fullName],()=>{})

watchEffect函数

  • 不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据

  • 默认初始时就会执行第一次, 从而可以收集需要监视的数据

  • 监视数据发生变化时回调

toRefs

把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref。

如果要在setup中获取值就需要 state.name.value

  1. <script lang="ts">
  2. import { defineComponent, reactive, toRefs } from "vue";
  3. export default defineComponent({
  4. name: "App",
  5. setup() {
  6. const state = reactive({
  7. name: "adekang",
  8. age: "18",
  9. });
  10. // const state2 = toRefs(state);
  11. const { name, age } = toRefs(state);
  12. setInterval(() => {
  13. // state.name += "-";
  14. name.value += "=";
  15. }, 1000);
  16. // return { state };
  17. // 不是响应式的
  18. return { name, age };
  19. },
  20. });
  21. </script>

ref获取元素

利用ref函数获取组件中的标签元素

功能需求: 让输入框自动获取焦点

  1. <template>
  2. <h2>App</h2>
  3. <input type="text">---
  4. <input type="text" ref="inputRef">
  5. </template>
  6. <script lang="ts">
  7. import { onMounted, ref } from 'vue'
  8. export default {
  9. setup() {
  10. const inputRef = ref<HTMLElement|null>(null)
  11. onMounted(() => {
  12. inputRef.value && inputRef.value.focus()
  13. })
  14. return {
  15. inputRef
  16. }
  17. },
  18. }
  19. </script>

Composition API(其它部分)

shallowReactive 与 shallowRef

  • shallowReactive : 只处理了对象内最外层属性的响应式(也就是浅响应式)
  • shallowRef: 只处理了value的响应式, 不进行对象的reactive处理
  • 什么时候用浅响应式呢?

    • 一般情况下使用ref和reactive即可
    • 如果有一个对象数据, 结构比较深, 但变化时只是外层属性变化 ===> shallowReactive
    • 如果有一个对象数据, 后面会产生新的对象来替换 ===> shallowRef
  1. <template>
  2. <h2>App</h2>
  3. <h3>m1: {{ m1 }}</h3>
  4. <h3>m2: {{ m2 }}</h3>
  5. <h3>m3: {{ m3 }}</h3>
  6. <h3>m4: {{ m4 }}</h3>
  7. <button @click="update">更新</button>
  8. </template>
  9. <script lang="ts">
  10. import { reactive, ref, shallowReactive, shallowRef } from "vue";
  11. export default {
  12. // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
  13. setup() {
  14. const m1 = reactive({ a: 1, b: { c: 2 } });
  15. const m2 = shallowReactive({ a: 1, b: { c: 2 } });
  16. const m3 = ref({ a: 1, b: { c: 2 } });
  17. const m4 = shallowRef({ a: 1, b: { c: 2 } });
  18. const update = () => {
  19. // 此处每个数据单独操作
  20. // m1.b.c += 1;
  21. m2.b.c += 1;
  22. // m3.value.a += 1;
  23. // m4.value.a += 1;
  24. };
  25. return {
  26. m1,
  27. m2,
  28. m3,
  29. m4,
  30. update,
  31. };
  32. },
  33. };
  34. </script>

readonly 与 shallowReadonly

  • readonly:

    • 深度只读数据
    • 获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。
    • 只读代理是深层的:访问的任何嵌套 property 也是只读的。
  • shallowReadonly

    • 浅只读数据
    • 创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换
  • 应用场景:

    • 在某些特定情况下, 我们可能不希望对数据进行更新的操作, 那就可以包装生成一个只读代理对象来读取数据, 而不能修改或删除

toRaw 与 markRaw

  • toRaw

    • 返回由 reactivereadonly 方法转换成响应式代理的普通对象。
    • 这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发界面更新。
  • markRaw

    • 标记一个对象,使其永远不会转换为代理。返回对象本身
    • 应用场景:

      • 有些值不应被设置为响应式的,例如复杂的第三方类实例或 Vue 组件对象。
      • 当渲染具有不可变数据源的大列表时,跳过代理转换可以提高性能。
  1. <template>
  2. <h2>{{state}}</h2>
  3. <button @click="testToRaw">测试toRaw</button>
  4. <button @click="testMarkRaw">测试markRaw</button>
  5. </template>
  6. <script lang="ts">
  7. import {
  8. markRaw,
  9. reactive, toRaw,
  10. } from 'vue'
  11. export default {
  12. setup () {
  13. const state = reactive<any>({
  14. name: 'tom',
  15. age: 25,
  16. })
  17. const testToRaw = () => {
  18. const user = toRaw(state)
  19. user.age++ // 界面不会更新
  20. }
  21. const testMarkRaw = () => {
  22. const likes = ['a', 'b']
  23. // state.likes = likes
  24. state.likes = markRaw(likes) // likes数组就不再是响应式的了
  25. setTimeout(() => {
  26. state.likes[0] += '--'
  27. }, 1000)
  28. }
  29. return {
  30. state,
  31. testToRaw,
  32. testMarkRaw,
  33. }
  34. }
  35. }
  36. </script>

toRef

  • 为原响应式对象上的某个属性创建一个 ref对象, 二者内部操作的是同一个数据值, 更新时二者是同步的

  • 区别ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响

  • 应用: 当要将 某个prop 的 ref 传递给复合函数时,toRef 很有用

customRef

  • 创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制
  • 需求: 使用 customRef 实现 debounce 的示例
  1. <template>
  2. <h2>App</h2>
  3. <input v-model="keyword" placeholder="搜索关键字"/>
  4. <p>{{keyword}}</p>
  5. </template>
  6. <script lang="ts">
  7. /*
  8. 需求:
  9. 使用 customRef 实现 debounce 的示例
  10. */
  11. import {
  12. ref,
  13. customRef
  14. } from 'vue'
  15. export default {
  16. setup () {
  17. const keyword = useDebouncedRef('', 500)
  18. console.log(keyword)
  19. return {
  20. keyword
  21. }
  22. },
  23. }
  24. /*
  25. 实现函数防抖的自定义ref
  26. */
  27. function useDebouncedRef<T>(value: T, delay = 200) {
  28. let timeout: number
  29. return customRef((track, trigger) => {
  30. return {
  31. get() {
  32. // 告诉Vue追踪数据
  33. track()
  34. return value
  35. },
  36. set(newValue: T) {
  37. clearTimeout(timeout)
  38. timeout = setTimeout(() => {
  39. value = newValue
  40. // 告诉Vue去触发界面更新
  41. trigger()
  42. }, delay)
  43. }
  44. }
  45. })
  46. }
  47. </script>

provide 与 inject

  • 实现跨层级组件(祖孙)间通信
  1. <template>
  2. <h1>父组件</h1>
  3. <p>当前颜色: {{color}}</p>
  4. <button @click="color='red'"></button>
  5. <button @click="color='yellow'"></button>
  6. <button @click="color='blue'"></button>
  7. <hr>
  8. <Son />
  9. </template>
  10. <script lang="ts">
  11. import { provide, ref } from 'vue'
  12. import Son from './Son.vue'
  13. export default {
  14. name: 'ProvideInject',
  15. components: {
  16. Son
  17. },
  18. setup() {
  19. const color = ref('red')
  20. provide('color', color)
  21. return {
  22. color
  23. }
  24. }
  25. }
  26. </script>
  1. <template>
  2. <div>
  3. <h2>子组件</h2>
  4. <hr>
  5. <GrandSon />
  6. </div>
  7. </template>
  8. <script lang="ts">
  9. import GrandSon from './GrandSon.vue'
  10. export default {
  11. components: {
  12. GrandSon
  13. },
  14. }
  15. </script>
  1. <template>
  2. <h3 :style="{color}">孙子组件: {{color}}</h3>
  3. </template>
  4. <script lang="ts">
  5. import { inject } from 'vue'
  6. export default {
  7. setup() {
  8. const color = inject('color')
  9. return {
  10. color
  11. }
  12. }
  13. }
  14. </script>

响应式数据的判断

  • isRef: 检查一个值是否为一个 ref 对象
  • isReactive: 检查一个对象是否是由 reactive 创建的响应式代理
  • isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
  • isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理