vue3中的Composition API及引用场景

114 阅读5分钟

Composition API(组合API)

ref

  • 作用: 定义一个响应式的数据

  • 语法: const xxx = ref(initValue)

    • 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)
    • JS中操作数据: xxx.value
    • 模板中读取数据: 不需要.value,直接:<div>{{xxx}}</div>
  • 备注:

    • 接收的数据可以是:基本类型、也可以是对象类型
    • 基本类型的数据:响应式依然是靠Object.defineProperty()getset完成的。
    • 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数—— reactive函数。
  • 代码👇👇👇👇👇👇

<template>
  <el-card>
    <h2>姓名:{{ name }}</h2>
    <h2>年龄:{{ age }}</h2>
    <h3>工作种类:{{ job.type }}</h3>
    <h3>工作薪水:{{ job.salary }}</h3>
    <button @click="changeInfo">修改人的信息</button>
  </el-card>
</template>

<script setup>
import { ref } from "vue";
let name = ref("张三");
let age = ref(18);
let job = ref({
  type: "前端工程师",
  salary: "30K",
});

function changeInfo() {
  // 修改响应式数据
  name.value = "李四";
  age.value = 48;
  console.log(job.value);
  job.value.type = "UI设计师";
  job.value.salary = "60K";
  console.log(name, age, job);
}
</script>

通常用于定义基本数据类型

reactive

  • 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)

  • 语法:const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)

  • reactive定义的响应式数据是“深层次的”。

  • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。

  • 代码👇👇👇👇👇👇

<template>
  <el-card>
    <h2>姓名:{{ person.name }}</h2>
    <h2>年龄:{{ person.age }}</h2>
    <h3>工作种类:{{ person.job.type }}</h3>
    <h3>工作薪水:{{ person.job.salary }}</h3>
    <h3>爱好:{{ person.hobby }}</h3>
    <h3>测试的数据c:{{ person.job.a.b.c }}</h3>
    <button @click="changeInfo">修改人的信息</button>
  </el-card>
</template>

<script setup>
import { reactive } from "vue";
let person = reactive({
  name: "张三",
  age: 18,
  job: {
    type: "前端工程师",
    salary: "30K",
    a: {
      b: {
        c: 666,
      },
    },
  },
  hobby: ["抽烟", "喝酒", "烫头"],
});

 // 修改响应式数据
function changeInfo() {
  person.name = "李四";
  person.age = 48;
  person.job.type = "UI设计师";
  person.job.salary = "60K";
  person.job.a.b.c = 999;
  person.hobby[0] = "学习";
}
</script>

通过api接口请求来的数据需要重新赋值可以通过 Object.assign()合并对象来重新赋值

Object.assign()批量将后台返回的数据变为响应式

  • 概念:用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
  • 用法👇👇👇👇👇👇
 Object.assign(target, ...sources)
 参数: target--->目标对象
       source--->源对象
       返回值:target,即目标对象

  • 说人话就是:将source这个对象的键值都赋给target(合并对象)

  • 代码👇👇👇👇👇👇

let detail = reactive({});
// 后台请求来的数据
const { data } = await detailApi({ contributor_id: 1 });
// 将后台数据的键值都赋给detail
detail = Object.assign(detail, data);

computed计算属性

  • 用法几乎与vue2一致
  • 代码👇👇👇👇👇👇
<template>
  <el-card>
    姓:<input type="text" v-model="person.firstName" />
    <br />
    名:<input type="text" v-model="person.lastName" />
    <br />
    <span>全名:{{ person.fullName }}</span>
    <br />
    全名:<input type="text" v-model="person.fullName" />
    <br />
    <div>计算ref的值{{ myFullName }}</div>
  </el-card>
</template>

<script setup>
import { ref, reactive, computed } from "vue";
let person = reactive({
  firstName: "张",
  lastName: "三",
});
let myFullName = ref();
//计算属性——简写(没有考虑计算属性被修改的情况)
/* person.fullName = computed(()=>{
				return person.firstName + '-' + person.lastName
			}) */

//计算属性——完整写法(考虑读和写)
person.fullName = computed({
  get() {
    return person.firstName + "-" + person.lastName;
  },
  set(value) {
    const nameArr = value.split("-");
    person.firstName = nameArr[0];
    person.lastName = nameArr[1];
  },
});
// 计算ref的值
myFullName = computed({
  get() {
    return person.firstName + person.lastName;
  },
  set(value) {
    person.firstName = nameArr[0];
    person.lastName = nameArr[1];
  },
});
</script>


computed函数的回调什么时候执行?

初始化的时候执行一次和所依赖的数据发生变化的时候执行

watch

  • watch的套路是:既要指明监视的属性,也要指明监视的回调。

情况一:监视ref所定义的一个响应式数据

情况二:监视ref所定义的多个响应式数据

情况三:监视ref定义的响应式对象

情况四:监视reactive所定义的一个响应式数据的全部属性

情况五:监视reactive所定义的一个响应式数据中的某个属性

情况六:监视reactive所定义的一个响应式数据中的某些属性

对应代码👇👇👇👇👇👇

<template>
  <el-card>
    <h2>当前求和为:{{ sum }}</h2>
    <button @click="sum++">点我+1</button>
    <hr />
    <h2>当前的信息为:{{ msg }}</h2>
    <button @click="msg += '!'">修改信息</button>
    <hr />
    <h2>姓名:{{ person.name }}</h2>
    <h2>年龄:{{ person.age }}</h2>
    <h2>薪资:{{ person.job.j1.salary }}K</h2>
    <button @click="person.name += '~'">修改姓名</button>
    <button @click="person.age++">增长年龄</button>
    <button @click="person.job.j1.salary++">涨薪</button>

    <br />
    <h2>ref定义的响应式对象情况</h2>
    <h2>姓名:{{ personRef.name }}</h2>
    <h2>年龄:{{ personRef.age }}</h2>
    <h2>薪资:{{ personRef.job.j1.salary }}K</h2>
    <button @click="personRef.name += '~'">修改姓名</button>
    <button @click="personRef.age++">增长年龄</button>
    <button @click="personRef.job.j1.salary++">涨薪</button>
  </el-card>
</template>

<script setup>
import { ref, reactive, watch } from "vue";
let sum = ref(0);
let msg = ref("你好啊");
let personRef = ref({
  name: "张三",
  age: 18,
  job: {
    j1: {
      salary: 20,
    },
  },
});
let person = reactive({
  name: "张三",
  age: 18,
  job: {
    j1: {
      salary: 20,
    },
  },
});

//情况一:监视ref所定义的一个响应式数据
watch(
  sum,
  (newValue, oldValue) => {
    console.log("sum变了", newValue, oldValue);
  },
  { immediate: true }
);


//情况二:监视ref所定义的多个响应式数据
watch(
  [sum, msg],
  (newValue, oldValue) => {
    console.log("sum或msg变了", newValue, oldValue);
  },
  { immediate: true }
);


/*
情况三:监视ref定义的响应式对象
  1.注意:由于person是通过ref定义的响应式对象,所以监听它的属性变化的时候需要使用.value才能监听到数据的变化
  2.注意:由于person是引用类型,在ref内部默认其value是reactive生成的一个Proxy对象,因此还可以添加深度监听来监听数据的变化
*/
// 以下两种写法任选其一
watch(personRef.value, (newValue, oldValue) => {
  console.log("person的值变化了", newValue, oldValue);
});
watch(
  personRef,
  (newValue, oldValue) => {
    console.log("person的值变化了", newValue, oldValue);
  },
  { deep: true } //此处的deep配置有效,但是oldValue还是获取不对
);


/*
情况四:监视reactive所定义的一个响应式数据的全部属性
		1.注意:此处无法正确的获取oldValue
		2.注意:强制开启了深度监视(deep配置无效)
			*/
watch(
  person,
  (newValue, oldValue) => {
    console.log("person变化了", newValue, oldValue);
  },
  { deep: false }
); //此处的deep配置无效


//情况五:监视reactive所定义的一个响应式数据中的某个属性
watch(
  () => person.name,
  (newValue, oldValue) => {
    console.log("person的name变化了", newValue, oldValue);
  }
);


//情况六:监视reactive所定义的一个响应式数据中的某些属性
watch([() => person.name, () => person.age], (newValue, oldValue) => {
  console.log("person的name或age变化了", newValue, oldValue);
});


//特殊情况
watch(
  () => person.job,
  (newValue, oldValue) => {
    console.log("person的job变化了", newValue, oldValue);
  },
  { deep: true }
); //此处由于监视的是reactive所定义的对象中的某个属性,所以deep配置有效
</script>

两个小“坑”:

  • 监视reactive定义的响应式数据时:oldValue无法正确获取(也变成了新数据)、强制开启了深度监视(deep配置失效)。
  • 监视reactive定义的响应式数据中某个属性时:deep配置有效。

watchEffect

  • watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。

  • watchEffect有点像computed:

    • 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
    • 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
  • 代码👇👇👇👇👇👇

<template>
  <el-card>
    <h2>当前求和为:{{ sum }}</h2>
    <button @click="sum++">点我+1</button>
    <hr />
    <h2>当前的信息为:{{ msg }}</h2>
    <button @click="msg += '!'">修改信息</button>
    <hr />
    <h2>姓名:{{ person.name }}</h2>
    <h2>年龄:{{ person.age }}</h2>
    <h2>薪资:{{ person.job.j1.salary }}K</h2>
    <button @click="person.name += '~'">修改姓名</button>
    <button @click="person.age++">增长年龄</button>
    <button @click="person.job.j1.salary++">涨薪</button>
  </el-card>
</template>

<script setup>
import { ref, reactive, watchEffect } from "vue";
let sum = ref(0);
let msg = ref("你好啊");
let person = reactive({
  name: "张三",
  age: 18,
  job: {
    j1: {
      salary: 20,
    },
  },
});

watchEffect(() => {
  const x1 = sum.value;
  const x2 = person.job.j1.salary;
  console.log("watchEffect所指定的回调执行了");
});
</script>

toRef与toRefs

  • 作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。

  • 语法:const name = toRef(person,'name')

  • 应用: 要将响应式对象中的某个属性单独提供给外部使用时。

  • 扩展:toRefstoRef功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)

vue3写法

  • 代码👇👇👇👇👇👇
<template>
  <h2>姓名:{{ name }}</h2>
  <h2>姓名:{{ name2 }}</h2>
  <h2>年龄:{{ age }}</h2>
  <h2>薪资:{{ job.j1.salary }}K</h2>
  <button @click="name += '~'">修改姓名</button>
  <button @click="age++">增长年龄</button>
  <button @click="job.j1.salary++">涨薪</button>
</template>

<script>
import { ref, reactive, toRef, toRefs } from "vue";
export default {
  name: "Demo",
  setup() {
    //数据
    let person = reactive({
      name: "张三",
      age: 18,
      job: {
        j1: {
          salary: 20,
        },
      },
    });
    const name2 = toRef(person,'name')
    //返回一个对象(常用)
    return {
      name2,
      /*
          下面三行代码等同于 ...toRefs(person)
      name:toRef(person,'name'),//将person对象中的name变为响应式属性
      age:toRef(person,'age'),
      salary:toRef(person.job.j1,'salary'),
      */
      ...toRefs(person),
    };
  },
};
</script>

setup形式写法

  • 代码👇👇👇👇👇👇
<template>
  <el-card>
    <h2>姓名:{{ name }}</h2>
    <h2>姓名:{{ name2 }}</h2>
    <h2>年龄:{{ age }}</h2>
    <h2>job:{{ job }}</h2>

    <h2>薪资:{{ job.j1.salary }}K</h2>
    <button @click="name += '~'">修改姓名</button>
    <button @click="age++">增长年龄</button>
    <button @click="job.j1.salary++">涨薪</button>
  </el-card>
</template>

<script setup>
import { toRef, reactive, toRefs } from "vue";
let person = reactive({
  name: "张三",
  age: 18,
  job: {
    j1: {
      salary: 20,
    },
  },
});

const name2 = toRef(person, "name");
const { name, age, job } = toRefs(person);
</script>

shallowReactive 与 shallowRef

  • shallowReactive:只处理对象最外层属性的响应式(浅响应式)。

  • shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。

  • 什么时候使用?

    • 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
    • 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。

readonly 与 shallowReadonly

  • readonly: 让一个响应式数据变为只读的(深只读)。
  • shallowReadonly:让一个响应式数据变为只读的(浅只读)。
  • 应用场景: 不希望数据被修改时。
  • 代码👇👇👇👇👇👇
<template>
  <el-card>
    <h2>姓名:{{ name }}</h2>
    <h2>年龄:{{ age }}</h2>
    <h2>job:{{ job }}</h2>

    <h2>薪资:{{ job.j1.salary }}K</h2>
    <button @click="name += '~'">修改姓名</button>
    <button @click="age++">增长年龄</button>
    <button @click="job.j1.salary++">涨薪</button>
  </el-card>
</template>

<script setup>
import { reactive, ref, readonly, toRefs } from "vue";
let sum = ref(0);
let person = reactive({
  name: "张三",
  age: 18,
  job: {
    j1: {
      salary: 20,
    },
  },
});

person = readonly(person);
sum = readonly(sum);
// person = shallowReadonly(person)
// sum = shallowReadonly(sum)
const { name, age, job } = toRefs(person);
</script>


toRaw 与 markRaw

  • toRaw:
    • 作用:将一个由reactive生成的响应式对象转为普通对象
    • 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
  • markRaw:
    • 作用:标记一个对象,使其永远不会再成为响应式对象。
    • 应用场景:
      1. 有些值不应被设置为响应式的,例如复杂的第三方类库等。
      2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
  • 代码👇👇👇👇👇👇
<template>
  <h4>当前求和为:{{ sum }}</h4>
  <button @click="sum++">点我++</button>
  <hr />
  <h2>姓名:{{ name }}</h2>
  <h2>年龄:{{ age }}</h2>
  <h2>薪资:{{ job.j1.salary }}K</h2>
  <h3 v-show="person.car">座驾信息:{{ person.car }}</h3>
  <button @click="name += '~'">修改姓名</button>
  <button @click="age++">增长年龄</button>
  <button @click="job.j1.salary++">涨薪</button>
  <button @click="showRawPerson">输出最原始的person</button>
  <button @click="addCar">给人添加一台车</button>
  <button @click="person.car.name += '!'">换车名</button>
  <button @click="changePrice">换价格</button>
</template>

<script setup>
import { ref, reactive, toRefs, toRaw, markRaw } from "vue";
let sum = ref(0);
let person = reactive({
  name: "张三",
  age: 18,
  job: {
    j1: {
      salary: 20,
    },
  },
});

const showRawPerson = () => {
  const p = toRaw(person);
  p.age++;
  console.log(p);
};

const addCar = () => {
  let car = { name: "奔驰", price: 40 };
  person.car = markRaw(car);
};

const changePrice = () => {
  person.car.price++;
  console.log(person.car.price);
};
const { name, age, job } = toRefs(person);
</script>

customRef

  • 作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。

  • 实现防抖效果

  • 代码👇👇👇👇👇👇

<template>
  <input type="text" v-model="keyword" />
  <h3>{{ keyword }}</h3>
</template>

<script setup>
import { ref, customRef } from "vue";
//自定义一个ref——名为:myRef
const myRef = (value, delay) => {
  let timer;
  // 一定要返回
  return customRef((track, trigger) => {
    // 一定要返回一个对象
    return {
      get() {
        console.log(`有人从myRef这个容器中读取数据了,我把${value}给他了`);
        track(); //通知Vue追踪value的变化(提前和get商量一下,让他认为这个value是有用的)
        return value;
      },
      set(newValue) {
        console.log(`有人把myRef这个容器中数据改为了:${newValue}`);
        clearTimeout(timer);
        timer = setTimeout(() => {
          value = newValue;
          trigger(); //通知Vue去重新解析模板
        }, delay);
      },
    };
  });
};

// let keyWord = ref('hello') //使用Vue提供的ref
let keyWord = myRef("hello", 500); //使用程序员自定义的ref
console.log(keyWord);
console.log(ref);
</script>

响应式数据的判断

  • isRef: 检查一个值是否为一个 ref 对象

  • isReactive: 检查一个对象是否是由 reactive 创建的响应式代理

  • isReadonly: 检查一个对象是否是由 readonly 创建的只读代理

  • isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

  • 代码👇👇👇👇👇👇

<script setup>
import {
  ref,
  reactive,
  toRefs,
  readonly,
  isRef,
  isReactive,
  isReadonly,
  isProxy,
} from "vue";

let car = reactive({ name: "奔驰", price: "40W" });
let sum = ref(0);
let car2 = readonly(car);
const { name, price } = toRefs(car);

console.log(isRef(sum)); //true
console.log(isReactive(car)); //true
console.log(isReadonly(car2)); //true
console.log(isProxy(car)); //true
console.log(isProxy(sum)); //false
</script>