Ref 和 Reactive 学习笔记

Ref 和 Reactive 学习笔记

先不说什么是ref和reactive是啥,先说个情景:

我们定义了一个变量叫message,然后我在模板中展示出来。同时我们设置了一个按钮,来改变这个变量的值:

<template>
  <div>
    <button @click="changeMsg">change</button>
    <div>{{ message }}</div>
  </div>
</template>
<script setup lang="ts">
let message: string = "我是message"
const changeMsg = () => {
   message = "我变了"
   console.log(message)
}
</script>

但是上面的例子点击按钮之后页面上的值并不会被改变,只有控制台中输出的值被改变了,这很对劲,因为并没有重新给模板重新赋值,如果想让模板也变,就得手动给模板重新赋值一遍,这个很烦~。所以vue搞了个骚的,用一种方式让我们可以不手动给模板重新赋值,模板上就能展示最新的内容。因此,ref() 和 reactive() 闪亮登场~

一.ref全家桶

1.1 ref()

推荐使用 ref() 函数来声明响应式状态,ref() 接收参数,并将其包裹在一个带有 .value 属性的 ref 对象中返回,对于上面的例子来说,我们可以做这样的修改:

<template>
  <div>
    <button @click="changeMsg">change</button>
    <div>{{ message }}</div>
  </div>
</template>
<script setup lang="ts">
import { ref } from "vue";
let message = ref("我是message")
const changeMsg = () => {
   message.value = "change msg"
   console.log(message)
}
</script>

目前,你去点击按钮的时候,你就会发现模板中的代码也跟着修改了。

Tips:有个很容易弄混和忽略的东西:.value;在函数中使用ref的时候一定要记得.value,但是在模板中使用ref时,我们需要附加 .value。为了方便起见,当在模板中使用时,ref会自动解包 (有一些注意事项)。

1.2 shallowRef()

ref()的浅层作用形式。和 ref() 不同,浅层 ref 的内部值将会原样存储和暴露,并且不会被深层递归地转为响应式。只有对 .value 的访问是响应式的。

<template>
  <div>
    <button @click="changeMsg1">change</button>
		<button @click="changeMsg2">change</button>
    <div>{{ message }}</div>
  </div>
</template>
 
<script setup lang="ts">
import { Ref, shallowRef } from 'vue'
type Obj = {
  name: string
}
let message: Ref<Obj> = shallowRef({
  name: "我是初始值"
})
 
const changeMsg1 = () => {
  message.value.name = '我不会被改变'
}
const changeMsg2 = () => {
  message.value = { name: "这样才会改变" }
}
</script>

Tips:说人话就是:message.value.name = '我不会被改变'这么改不行!message.value = { name: "这样才会改变" }这么改才行.

同时还要注意,ref()shallowRef()是不可以在一起用的,否则DOM会被强制更新(具体原因往下开)。

1.3 customRef()

创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式

customRef() 预期接收一个工厂函数作为参数,这个工厂函数接受 tracktrigger 两个函数作为参数,并返回一个带有 getset 方法的对象。

一般来说,track() 应该在 get() 方法中调用,而 trigger() 应该在 set() 中调用。然而事实上,你对何时调用、是否应该调用他们有完全的控制权。

<template>
 
  <div ref="div">小满Ref</div>
  <hr>
  <div>
    {{ name }}
  </div>
  <hr>
  <button @click="change">修改 customRef</button>
 
</template>
 
<script setup lang='ts'>
import { ref, reactive, onMounted, shallowRef, customRef } from 'vue'
 
function myRef<T = any>(value: T) {
  let timer:any;
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newVal) {
        clearTimeout(timer)
        timer =  setTimeout(() => {
          console.log('触发了set')
          value = newVal
          trigger()
        },500)
      }
    }
  })
}
 
const name = myRef<string>('小满')
 
const change = () => {
  name.value = '大满'
}
</script>
<style scoped>
</style>

Tips:说实话,才疏学浅没看懂....上一段满神的代码自己悟吧。我只能理解到我自己写了一个方法实现响应式,然后在创建响应式的时候可以高一些骚操作

1.4 triggerRef()

强制触发依赖于一个浅层 ref 的副作用,这通常在对浅引用的内部值进行深度变更后使用。强制更新页面DOM

<template>
  <div>
    <button @click="changeMsg">change</button>
    <div>{{ message }}</div>
  </div>
</template>
 
<script setup lang="ts">
import { Ref, shallowRef,triggerRef } from 'vue'
type Obj = {
  name: string
}
let message: Ref<Obj> = shallowRef({
  name: "小满"
})
 
const changeMsg = () => {
  message.value.name = '大满'
 triggerRef(message)
}
</script> 

Tips:这个就是为什么上面 ref 和 shallowRef 为什么不可以一起用的原因,因为在ref中他会默认调用triggerRef,所以数据就强制更新了。

1.5 isRef()

这东西没啥好说的,检查某个值是否为 ref。是就返回True,不是就返回False

let foo: unknown
if (isRef(foo)) {
  // foo 的类型被收窄为了 Ref<unknown>
  foo.value
}

二.reactive

2.1 reactive()

还有另一种声明响应式状态的方式,即使用 reactive() API。与将内部值包装在特殊对象中的 ref 不同,reactive() 将使对象本身具有响应性:

import { reactive } from 'vue'
let person = reactive({
   name:"aaa"
})
person.name = "aaa"

Tips:首先说明,reactive()不能处理基本数据类型,写就报错,其次:如果用ref去绑定对象 或者 数组 等复杂的数据类型 我们看源码里面其实也是 去调用reactive,使用reactive 去修改值无须.value

但是对于一个数组来说,这样赋值页面是不会变化的因为会脱离响应式:

let person = reactive<number[]>([])
setTimeout(() => {
  person = [1, 2, 3]
  console.log(person);
  
},1000)

有两种方式可以解决这个问题,1:使用list.push(),别问我为啥不也不知道...

第二种我知道:就直接把他当做对象的一个属性搞,就别把他当数组,对就当对象,万事大吉~

type Person = {
  list?:Array<number>
}
let person = reactive<Person>({
   list:[]
})
setTimeout(() => {
  const arr = [1, 2, 3]
  person.list = arr;
  console.log(person);
},1000)

2.2 shallowReactive()

效果同shallowRef()只能对浅层的数据 如果是深层的数据只会改变值 不会改变视图:

这俩按钮,第二个就会不好用

<template>
  <div>
    <div>{{ state }}</div>
    <button @click="change1">test1</button>
    <button @click="change2">test2</button>
  </div>
</template>
 
<script setup lang="ts">
import { shallowReactive } from 'vue'
 
const obj = {
  a: 1,
  first: {
    b: 2,
    second: {
      c: 3
    }
  }
}

const state = shallowReactive(obj)
 
function change1() {
  state.a = 7
}
function change2() {
  state.first.b = 8
  state.first.second.c = 9
  console.log(state);
}
</script> 

三.To...

3.1 toRef

可以将值、refs 或 getters 规范化为 refs (3.3+)。

也可以基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。

toRef()接受两个参数,一个是你要结构的对象,一个是对象中某个属性的Key.

<template>
   <div>
      <button @click="change">按钮</button>
      {{state}}
   </div>
</template>
 
<script setup lang="ts">
import { reactive, toRef } from 'vue'
 
const obj = {
   foo: 1,
   bar: 1
}
 
const state = toRef(obj, 'bar')
// bar 转化为响应式对象
 
const change = () => {
   state.value++
   console.log(obj, state);
 
}
</script>

Tips:这个东西换成人话就是:把响应式的对象中的某个属性结构出来,并且结构出来的还是响应式的。

注意:你结构的对象一定得是响应式的才行,用toRef结构一个普通对象是没用的

3.2 toRefs

toRef是解构出一个,toRefs就解构出一堆:

import { reactive, toRefs } from 'vue'
const obj = reactive({
   foo: 1,
   bar: 1
})
 
let { foo, bar } = toRefs(obj)
 
foo.value++
console.log(foo, bar);

3.3 toRaw

他更简单,是就是把响应式的搞成普通的

import { reactive, toRaw } from 'vue'
 
const obj = reactive({
   foo: 1,
   bar: 1
})
const state = toRaw(obj)
// 响应式对象转化为普通对象
const change = () => {
   console.log(obj, state);
}
posted @ 2023-08-12 23:18  抓哇攻城狮  阅读(29)  评论(0)    收藏  举报