响应式:声明响应式数据 ref &reactive & readonly

ref 系列

ref() 深层响应式

接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个 .value property,指向该内部值

ref() 可以持有任何类型的值,包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构,比如 Map
ref() 去绑定对象 或者 数组 等复杂的数据类型,看源码其实也是去调用reactive

使用
<template>
  <!-- 在模板中使用 ref 时,我们不需要附加 .value。为了方便起见,当在模板中使用时,ref 会自动解包 -->
  <p>{{ count }}</p>
  <button @click="onAdd">add</button>
</template>

<script setup lang="ts">
  import { ref } from 'vue'

  const count = ref(0)

  // ref() 接收参数,并将其包裹在一个带有 .value 属性的 ref 对象中返回
  console.log(count) // { value: 0 }
  console.log(count.value) // 0

  const onAdd = () => {
    // 注意:被ref包装之后需要 .value 来进行赋值
    count.value = count.value + 1
  }
</script>
结合 TS
const str = ref('') // 会自动识别为 string 类似

const str:Ref<string> = ref('')

// 或
const str = ref<string>('')
自动解包场景
作为 文本插值
<template><p>{{ count }}</p></template>

<script setup lang="ts">
  import { ref } from 'vue'
  const count = ref(0)
</script>
作为顶级属性
const object = { id: ref(1) }
// object.id => [object Object] ref 对象

const { id } = object
id + 1  // => 2
作为 reactive 对象的属性

一个 ref 会在作为响应式对象的属性被访问或修改时自动解包:

const count = ref(0)
const state = reactive({
  count
})

console.log(state.count) // 0

// 注意:修改 state.count 会影响 count
state.count = 1
console.log(count.value) // 1

如果将一个新的 ref 赋值给一个关联了已有 ref 的属性,那么它会替换掉旧的 ref:

const otherCount = ref(2)

state.count = otherCount // 将一个新的 ref 赋值给一个关联了已有 ref 的属性
console.log(state.count) // 2

console.log(count.value) // 1,原始 ref count 与 state.count 失去联系

当 ref 作为响应式数组或原生集合类型(如 Map) 中的元素被访问时,它不会被解包:

const books = reactive([ref('Vue 3 Guide')])
// 这里需要 .value
console.log(books[0].value)

const map = reactive(new Map([['count', ref(0)]]))
// 这里需要 .value
console.log(map.get('count').value)

Ref

TS对应的接口

interface Ref<T> {
  value: T
}
import { ref, Ref } from 'vue'

let message: Ref<string | number> = ref("我是message")

isRef()

判断是不是一个ref对象

<template>
	<!-- template 中 count 自动识别为 count.value  -->
	<p>{{count}}</p>
</template>

<script setup lang="ts">
  import { ref, isRef } from 'vue'
  
  const count = ref(0)
  const num = 1

	console.log(isRef(count)) // true
	console.log(isRef(num)) // false
</script>

shallowRef() 浅层响应式

浅响应。创建一个跟踪自身 .value 变化的 ref,但不会使其值变成响应式的。

修改值不会触发视图更新

修改数据只会修改值得变更,不会影响视图。

<template>
  <p>{{ info.name }}--{{ info.age }}</p>
  <button @click="onChange">change</button>
</template>

<script setup lang="ts">
  import { shallowRef } from 'vue'

  const info = shallowRef({
    name: 'www',
    age: 18,
  })

  const onChange = () => {
    info.value.name = 'abc' // 不会进行响应式变更
    console.log(info)
  }
</script>

但是 重新赋值会触发更新

<template>
  <p>{{ info.name }}--{{ info.age }}</p>
  <button @click="onChange">change</button>
</template>

<script setup lang="ts">
  import { shallowRef } from 'vue'

  const info = shallowRef({
    name: 'www',
    age: 18,
  })

  const onChange = () => {
    info.value = { name: 'abc', age: 10 }
    console.log(info)
  }
</script>
shallowRef()ref() 同时使用会强制更新视图

由于 ref() 底层会调用 triggerRef()triggerRef() 会强制更新视图。所以 shallowRef()ref 同时使用也会更新视图。

<template>
  <p>{{ info.name }}--{{ info.age }} --- {{ count }}</p>
  <button @click="onChange">change</button>
</template>

<script setup lang="ts">
  import { shallowRef, ref } from 'vue'

  const info = shallowRef({
    name: 'www',
    age: 18,
  })

  const count = ref(0)
  const onChange = () => {
    info.value.name = 'abc' // 不会进行响应式变更

    count.value++ // 响应式变更
    console.log(info)
  }
</script>

在这里插入图片描述

triggerRef()

强制更新页面DOM

<template>
	<p>{{info.name}}--{{info.age}}</p>
	<button @click="onChange">change</button>
</template>

<script setup lang="ts">
  import { shallowRef, triggerRef } from 'vue'
  
  const info = shallowRef({
    name: 'www',
    age: 18
  })
  const onChange = () => {
    info.value.name = 'abc' // 不会进行响应式变更
    triggerRef(info)
  }
</script>

customRef()

自定义ref

customRef 是个工厂函数要求我们返回一个对象 并且实现 get 和 set 适合去做防抖之类的

<template>
  <p>{{ message }}</p>
  <input type="text" v-model="message" />
</template>

<script setup lang="ts">
  import { customRef } from 'vue'

  function myRef<T>(value: T) {
    let timer: ReturnType<typeof setTimeout> | undefined
    return customRef((track, trigger) => {
      return {
        get() {
          track()
          return value
        },
        set(newVal) {
          // 采用防抖赋值的结果有问题,以下采用节流
          timer && clearTimeout(timer)
          timer = setTimeout(() => {
            trigger()
            value = newVal
            clearTimeout(timer)
            timer = undefined
          }, 1000)
        },
      }
    })
  }
  const message = myRef('123')
</script>

toRef()

应用场景:有个函数需要接收 ref 响应式数据,但是 值 却是 reactive 。这个时候就可将 reactive 转成 ref

将数据变成 Ref 响应式数据

<template>
  <p>info: {{ info.name }}--{{ info.age }}</p>
  <p>person: {{ personName }}</p>
  <button @click="onChange">change</button>
</template>

<script setup lang="ts">
  import { reactive, toRef } from 'vue'

  const info = reactive({
    name: 'www',
    age: 18,
  })
  const personName = toRef(info, 'name')

  const onChange = () => {
    personName.value = 'abc'
    console.log(info, personName)
  }
</script>

在这里插入图片描述

注意:如果原始对象是非响应式的就不会更新视图 数据是会变的

<template>
  <p>info: {{ info.name }}--{{ info.age }}</p>
  <p>person: {{ personName }}</p>
  <button @click="onChange">change</button>
</template>

<script setup lang="ts">
  import { toRef } from 'vue'

  const info = {
    name: 'www',
    age: 18,
  }
  const personName = toRef(info, 'name')

  const onChange = () => {
    personName.value = 'abc'
    console.log(info, personName)
  }
</script>

在这里插入图片描述

toRefs()

批量将数据变成 Ref 响应式数据

<template>
  <p>info: {{ info.name }}--{{ info.age }}</p>
  <p>person: {{ personName }}---{{ personAge }}</p>
  <button @click="onChange">change</button>
</template>

<script setup lang="ts">
  import { reactive, toRefs } from 'vue'

  const info = reactive({
    name: 'www',
    age: 18,
  })
  const { name: personName, age: personAge } = toRefs(info)

  const onChange = () => {
    personName.value = 'abc'
    personAge.value = 20
    console.log(info, personName)
  }
</script>

注意:如果原始对象是非响应式的就不会更新视图 数据是会变的

<template>
  <p>info: {{ info.name }}--{{ info.age }}</p>
  <p>person: {{ personName }}---{{ personAge }}</p>
  <button @click="onChange">change</button>
</template>

<script setup lang="ts">
  import { toRefs } from 'vue'

  const info = {
    name: 'www',
    age: 18,
  }
  const { name: personName, age: personAge } = toRefs(info)

  const onChange = () => {
    personName.value = 'abc'
    personAge.value = 20
    console.log(info, personName)
  }
</script>

reactive 系列

参考

reactive()

用来绑定复杂的数据类型,例如 对象 数组。

使用reactive 去修改值无须 .value

使用
<template>
  <button @click="state.count++">
    {{ state.count }}
  </button>
</template>

<script setup lang="ts">
  import { reactive } from 'vue'
	
  // reactive() 将对象本身具有响应性
  const state = reactive({ count: 0 })
</script>

响应式对象是 JavaScript 代理,其行为就和普通对象一样。不同的是,Vue 能够拦截对响应式对象所有属性的访问和修改,以便进行依赖追踪和触发更新。

结合 TS
interface Book {
  title: string
  year?: number
}

const str:Book = reactive({ title: '' })

// 或
const str = reactive<Book>({ title: '' })
局限性
不可以绑定普通的数据类型

它只能用于对象类型 (对象、数组和如 MapSet 这样的集合类型)。它不能持有如 stringnumberboolean 这样的原始类型

<template>
  <p>{{ count }}</p>
</template>

<script setup lang="ts">
  import { reactive } from 'vue'
  const count = reactive(2)
</script>

在这里插入图片描述

查看源码: reactive 继承与对象
在这里插入图片描述

不能重新赋值

reactive 数据 重新赋值会丢失响应式

由于 Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用。这意味着我们不能轻易地 “替换” 响应式对象,因为这样的话与第一个引用的响应性连接将丢失:

<template>
  <p>{{ info.name }}--{{ info.age }}</p>
  <button @click="onChange">change</button>
</template>

<script setup lang="ts">
  import { reactive } from 'vue'

  let info = reactive({
    name: 'www',
    age: 18,
  })
  const onChange = () => {
    // 响应性连接已丢失!
    info = { name: 'abc', age: 20 }
    console.log(info)
  }
</script>

解决方法:

数组:

  • 使用数组方法
  • 将数组放到对象中,作为对象的属性去赋值
<template>
  <p>{{ arr }}</p>
  <button @click="onChange">change</button>
</template>

<script setup lang="ts">
  import { reactive } from 'vue'

  let arr = reactive<number[]>([])
  const onChange = () => {
    // arr = [1, 2, 3] // 不会进行响应式变更
    console.log(arr)

    // 解决方法:
    // 使用数组方法
    // arr.push(1, 2, 3)
    arr.splice(0, 0, 1, 2, 3)
  }
</script>

对象:

  • 一个个属性赋值
  • 在外层再包裹一层对象
<template>
  <p>{{ info.name }}--{{ info.age }}</p>
  <button @click="onChange">change</button>
</template>

<script setup lang="ts">
  import { reactive } from 'vue'

  let info = reactive({
    name: 'www',
    age: 18,
  })
  const onChange = () => {
    info = { name: 'abc', age: 20 } // 不会进行响应式变更
    console.log(info)
    
    // 解决方法
    info.name = 'abc'
    info.age = 20
  }
</script>
对解构操作不友好

当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连接:

const state = reactive({ count: 0 })

// 当解构时,count 已经与 state.count 断开连接
let { count } = state
// 不会影响原始的 state
count++

// 该函数接收到的是一个普通的数字
// 并且无法追踪 state.count 的变化
// 我们必须传入整个对象以保持响应性
callSomeFunction(state.count)

由于这些限制,我们建议使用 ref() 作为声明响应式状态的主要 API。

注意
const raw = {} // 原始对象
const proxy = reactive(raw) // 代理对象:返回的是一个原始对象的 Proxy

// 代理对象和原始对象不是全等的
console.log(proxy === raw) // false

// 在同一个对象上调用 reactive() 会返回相同的代理
console.log(reactive(raw) === proxy) // true

// 在一个代理上调用 reactive() 会返回它自己
console.log(reactive(proxy) === proxy) // true

依靠深层响应性,响应式对象内的嵌套对象依然是代理:

const proxy = reactive({})

const raw = {}
proxy.nested = raw

console.log(proxy.nested === raw) // false

shallowReactive

只能对浅层的数据 如果是深层的数据只会改变值 不会改变视图

注意:shallowReactive 不能和 reactive 同时使用,否则 shallowReactive 也会更新视图。

官方回复:https://github.com/vuejs/core/issues/5623

<template>
  <p>{{ info.name }}--{{ info.score }}</p>
  <button @click="onChange">change</button>
</template>

<script setup lang="ts">
  import { shallowReactive } from 'vue'

  let info = shallowReactive({
    name: 'www',
    score: {
      math: 90,
      english: 80,
      chinese: 95,
    },
  })
  const onChange = () => {
    // 不会进行响应式变更
    info.score.math = 10
    console.log(info)
  }
</script>

在这里插入图片描述

readonly

接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。

类型:

function readonly<T extends object>(
  target: T
): DeepReadonly<UnwrapNestedRefs<T>>

详细信息:

只读代理是深层的:对任何嵌套属性的访问都将是只读的。它的 ref 解包行为与 reactive() 相同,但解包得到的值是只读的。

要避免深层级的转换行为,请使用 shallowReadonly() 作替代。

示例:

<template>
  <p>{{ info.name }}--{{ info.age }}</p>
</template>

<script setup lang="ts">
  import { reactive, readonly } from 'vue'
  const info = reactive({
    name: 'www',
    age: 18,
  })
  const copy = readonly(info)

  // 用来做响应性追踪
  watchEffect(() => {
    console.log(copy.name)
  })

  // 更改源属性会触发其依赖的侦听器
  info.age = 20

  // 更改该只读副本将会失败,并会得到一个警告
  copy.name = 'abc'
</script>

在这里插入图片描述

toRaw

将响应式对象转化为普通对象

<template>
  <p>info: {{ info.name }}--{{ info.age }}</p>
  <p>person: {{ person }}</p>
  <button @click="onChange">change</button>
</template>

<script setup lang="ts">
  import { reactive, toRaw } from 'vue'

  const info = reactive({
    name: 'www',
    age: 18,
  })

  const person = toRaw(info)

  const onChange = () => {
    person.name = 'abc'
    person.age = 20
    console.log(info, person)
  }
</script>

在这里插入图片描述

  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值