reactive()

在组合式 API 中,如果我们想要创建一个响应式的对象(数据发生变化视图也会变化就是响应式)需要使用reactive()方法。

  1. import { reactive } from "vue";
  2. export default {
  3. setup() {
  4. const data = {
  5. a: 1,
  6. b: {
  7. c: 2
  8. }
  9. };
  10. const state = reactive(data);
  11. console.log(state);
  12. }
  13. };

image.png
reactive()方法返回一个「代理对象」,该代理对象和「原对象」不是同一个引用。只有代理对象是响应式的,更改原始对象不会触发更新。

  1. setup() {
  2. const data = {
  3. a: 1,
  4. b: {
  5. c: 2
  6. }
  7. };
  8. const state = reactive(data);
  9. console.log(state == data); // false
  10. }

当一个被**reactive()**方法「包装过」的对象再次被包装,会返回第一次被包装的代理商对象。简单说就是无论包装多少次都会返回第一次被包装的state对象,是相同的引用:

  1. setup() {
  2. const data = {
  3. a: 1,
  4. b: {
  5. c: 2
  6. }
  7. };
  8. const state = reactive(data);
  9. const state1 = reactive(state);
  10. const state2 = reactive(state);
  11. console.log(state === state1); // true
  12. console.log(state === state2); // true
  13. }

isReactive()

可以通过isReactive()方法来判断一个对象是不是reactive()包装过的对象:

  1. import { reactive, isReactive } from "vue";
  2. export default {
  3. setup() {
  4. const data = {
  5. a: 1,
  6. b: {
  7. c: 2
  8. }
  9. };
  10. const state = reactive(data);
  11. console.log(isReactive(state)); // true
  12. }
  13. };

shallowReactive()

shallowReactive()reactive()方法作用相同也是把一个对象包装为一个响应式的数据。但shallowReactive()包装后的数据是浅层响应式的,reactive()是深层的。

  1. import { reactive, shallowReactive, isReactive } from "vue";
  2. export default {
  3. setup() {
  4. const data = {
  5. a: 1,
  6. b: {
  7. c: 2
  8. }
  9. };
  10. const state = shallowReactive(data);
  11. // 使用 isReactive() 来判断
  12. console.log(isReactive(state)); // true
  13. console.log(isReactive(state.b)); // false
  14. setTimeout(() => {
  15. state.b.c = "Test";
  16. console.log(state)
  17. }, 1000);
  18. return {
  19. state
  20. };
  21. }
  22. };

屏幕录制2023-06-12 15.39.01.gif
可以看到,1 秒后数据发生了变化,但是页面没有进行更新!!!

reactive() 的局限性

1、只能针对 Array、Map、Set、Object 这样的引用类型有效,无法包装 String、Number、Boolean 这样的原始类型。
2、因为 Vue 的响应式系统是通过属性访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。这意味着我们不可以随意地“替换”一个响应式对象,因为这将导致对初始引用的响应性连接丢失:

  1. let state = reactive({ count: 0 })
  2. // 上面的引用 ({ count: 0 }) 将不再被追踪(响应性连接已丢失!)
  3. state = reactive({ count: 1 })

所以通常需要包一个最外层大对象:

  1. let state = reactive({
  2. data: { count: 0 }
  3. })
  4. state.data = { count: 1 }; // 这样 data 始终都是响应式的

3、不能把属性进行解构单独使用,这样也会丢失响应式

  1. const state = reactive({ count: 0 })
  2. // n 是一个局部变量,同 state.count
  3. // 失去响应性连接
  4. let n = state.count
  5. // 不影响原始的 state
  6. n++
  7. // count 也和 state.count 失去了响应性连接
  8. let { count } = state
  9. // 不会影响原始的 state
  10. count++
  11. // 该函数接收一个普通数字,并且
  12. // 将无法跟踪 state.count 的变化
  13. callSomeFunction(state.count)

因为当我们把对象属性解构后使用,就单纯的拿到属性的值,不会再触发对象属性的 setter/getter 机制了。
所以,ref()函数就诞生了,ref()就是为了解决所有值类型都能使用 “引用” 机制。

ref()

ref()是针对所有值的定制化的包装引用!包装响应式的同时还可以进行相应的操作和传递,例如解构、函数传参数。

  1. import { ref } from "vue";
  2. export default {
  3. setup() {
  4. const title = ref("This is title.");
  5. console.log(title);
  6. }
  7. };

image.png
通过ref()函数包装过的数据会返回一个对象,对象的属性分别有:

  • dep:数据的依赖;
  • __v_isRef:是否是 Ref 对象;
  • __v_isShallow:是否是浅响应式;
  • _rawValue:数据定义时的值;
  • _value:当前动态的值;
  • value:值

和响应式对象的属性类似,ref 的.value属性也是响应式的。同时,当值为对象类型时,会用**reactive()**自动转换它的**.value**

  1. const objectRef = ref({ count: 0 });
  2. console.log(objectRef);

image.png
可以看到,.value属性依然一个被代理过的对象。

一个包含对象类型值的 ref 可以响应式地替换整个对象:

  1. const objectRef = ref({ count: 0 })
  2. // 这是响应式的替换
  3. objectRef.value = { count: 1 }

简言之,ref()让我们能创造一种对任意值的 “引用”,并能够在不丢失响应性的前提下传递这些引用。

ref() 自动解包

在模版中解包

当 ref 在模板中作为顶层属性被访问时,它们会被自动“解包”,所以不需要使用.value

  1. <template>
  2. <button @click="increment">
  3. <!-- 无需 .value -->
  4. {{ count }}
  5. </button>
  6. </template>
  7. <script>
  8. import { ref } from "vue";
  9. export default {
  10. setup() {
  11. const count = ref(0);
  12. function increment() {
  13. count.value++;
  14. }
  15. return {
  16. count,
  17. increment
  18. };
  19. }
  20. };
  21. </script>

:::warning ⚠️ 注意
请注意,仅当 ref 是模板渲染上下文的顶层属性时才适用自动“解包”。 例如, object是顶层属性,但object.foo不是。 :::

  1. <template>
  2. <button @click="increment">
  3. {{ object.foo + 1 }}
  4. </button>
  5. </template>
  6. <script>
  7. import { ref } from "vue";
  8. export default {
  9. setup() {
  10. const object = { foo: ref(1) };
  11. function increment() {
  12. object.count.value++;
  13. }
  14. return {
  15. object,
  16. increment
  17. };
  18. }
  19. };
  20. </script>

image.png
这是因为object.foo是一个对象,她会调用toString()方法进行计算。
我们可以把foo进行解构后返回:

  1. const { foo } = object

需要注意的是,如果一个 ref 是文本插值(即一个{{ }}符号)计算的最终值,它也将被解包。因此下面的渲染结果将为 1:

  1. {{ object.foo }}

在对象中解包

当使用**reactive()**包装一个「对象」的时,如果对象的属性值是一个 ref 对象,ref 对象会自动把**.value**提取出来,这和普通属性是一样的:

  1. import { ref, reactive } from "vue";
  2. export default {
  3. setup() {
  4. const count = ref(0);
  5. const state = reactive({ count });
  6. console.log(count, state);
  7. // 不需要使用 state.count.value
  8. console.log(state.count);
  9. }
  10. };

image.png

虽然进行了解包,但count依然是同步的:

  1. setup() {
  2. const count = ref(0);
  3. const state = reactive({ count });
  4. console.log(count, state.count); // ref, 0
  5. setTimeout(() => {
  6. count.value = 3;
  7. console.log(count, state.count); // ref, 3
  8. }, 1000);
  9. }

屏幕录制2023-06-12 16.18.55.gif

跟响应式对象不同,当 ref 作为响应式数组或像 Map 这种原生集合类型的元素被访问时,不会进行自动解包。

  1. const books = reactive([ref('Vue 3 Guide')])
  2. // 这里需要 .value
  3. console.log(books[0].value);
  4. const map = reactive(new Map([['count', ref(0)]]))
  5. // 这里需要 .value
  6. console.log(map.get('count').value)

isRef()

我们可以使用isRef()方法来判断一个数据是不是 ref 对象:

  1. const count = ref(0);
  2. console.log(count.value); // 0
  3. console.log(isRef(count)); // true

shallowRef()

ref()方法一样,shallowRef()也可以把数据包装为 ref 对象,只不过是浅层的包装:

  1. import { isRef, shallowRef } from "vue";
  2. export default {
  3. setup() {
  4. const state = shallowRef({
  5. a: 1,
  6. b: {
  7. c: 2
  8. }
  9. });
  10. console.log(state);
  11. console.log(isRef(state)); // true
  12. console.log(isRef(state.b)); // false
  13. }
  14. };

toRef() 与 toRefs()

前面我们说过,当使用reactive()包装对象后我们不能把对象的属性进行解构使用,因为这会导致响应式的丢失,所以我们可以使用toRefs()把对象的属性全部转换为响应式的属性:

  1. import { ref, isRef, reactive, toRefs } from "vue";
  2. export default {
  3. setup() {
  4. const state = reactive({
  5. a: 1,
  6. b: {
  7. c: 2
  8. }
  9. });
  10. const { a, b } = toRefs(state);
  11. console.log(a, b);
  12. }
  13. };

image.png
这样,ab就都是响应式的啦。

如果你不想把所有的属性都进行解构,可以使用toRef()只把某一个属性进行包装为响应式的数据:

  1. import { ref, isRef, reactive, toRefs, toRef } from "vue";
  2. export default {
  3. setup() {
  4. const state = reactive({
  5. a: 1,
  6. b: {
  7. c: 2
  8. }
  9. });
  10. const res = toRef(state, "a");
  11. console.log(res);
  12. }
  13. };

image.png

unref()

如果参数是 ref,则返回内部值,否则返回参数本身。
这是val = isRef(val) ? val.value : val计算的一个语法糖。

  1. import { ref, unref } from "vue";
  2. export default {
  3. setup() {
  4. const formData = ref({
  5. username: "xiechen",
  6. password: "123456"
  7. });
  8. console.log(formData, unref(formData));
  9. }
  10. };

image.png