Vue 3中Composition API浅析

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里面的一些属性

image-20210524190843327

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

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>
复制代码

下面是输出的信息

image-20210524195113371

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>
复制代码

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

image-20210524203303721

如若再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 中的 propscontext。这样就很清楚的发现有什么了。

image-20210524204619844

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 };
  },
复制代码

image-20210524211119995

同时也支持 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
    • 返回由 reactivereadonly 方法转换成响应式代理的普通对象。
    • 这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发界面更新。
  • 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 方法创建的代理

资料来源:24kcs.github.io/vue3_study/

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享