整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

vue3-响应式基础之ref

明响应式状态

ref()

在组合式 API 中,推荐使用 ref() 函数来声明响应式状态: ref() 接收参数,并将其包裹在一个带有 .value 属性的 ref 对象中返回:

import { ref } from 'vue'
const count = ref(0)

console.log(count) // { value: 0 }
console.log(count.value) // 0

count.value++
console.log(count.value) // 1

形式1 setup() 函数

  1. 要在组件模板中访问 ref,请从组件的 setup() 函数中声明并返回它们:
<script lang="ts" >
import { ref } from 'vue'

export default {
    setup() {
        const count = ref(0)

        function increment() {
            // 在 JavaScript 中需要 .value
            count.value++
        }

        // 不要忘记同时暴露 increment 函数
        return {
            count,
            increment
        }
    }
}
</script>

<template>
    <div class="container">
        <div>{{ count }}</div>
        <button @click="count++">
            {{ count }}
        </button>
    </div>
</template>

<style  scoped>
.container {}
</style>
  1. 注意,在模板中使用 ref 时,我们不需要附加 .value。为了方便起见,当在模板中使用时,ref 会自动解包 (有一些注意事项)。

在模板渲染上下文中,只有顶级的 ref 属性才会被解包。

在下面的例子中,count 和 object 是顶级属性,但 object.id 不是:

const count = ref(0)
const object = { id: ref(1) }

//模版正常渲染执行
{{ count + 1 }} 

//模版不会正常渲染非顶级不会被解包仍然是一个ref
{{ object.id + 1 }}  对象


//我们可以将 id 解构为一个顶级属性
const { id } = object
{{ id + 1 }}   //模版正常渲染并执行

//模版自动解包
{{ object.id }}
该特性仅仅是文本插值的一个便利特性,等价于 {{ object.id.value }}

形式2 <script setup>

  • 在 setup() 函数中手动暴露大量的状态和方法非常繁琐。
  • 幸运的是,我们可以通过使用单文件组件 (SFC) 来避免这种情况。我们可以使用 <script setup> 来大幅度地简化代码:
<script setup lang="ts">
import { ref } from 'vue'

const count = ref(0)

function increment() {
    count.value++
}
</script>

<template>
    <button @click="increment">
        {{ count }}
    </button>
</template>

深层响应性

  • Ref 可以持有任何类型的值,包括深层嵌套的对象、数组或者 JavaScript 内置的数据结构,比如 Map。
  • Ref 会使它的值具有深层响应性。这意味着即使改变嵌套对象或数组时,变化也会被检测到:
<script setup lang="ts">
import { ref } from 'vue'

const count = ref(0)
const obj = ref({
    nested: { count: 0 },
    arr: ['foo', 'bar']
})

function mutateDeeply() {
    // 以下都会按照期望工作
    obj.value.nested.count++
    obj.value.arr.push('baz')
}


function increment() {
    count.value++
}
</script>

<template>
    {{ obj.arr }}
    <button @click="mutateDeeply">
        {{ obj.nested.count + 1 }}
    </button>
</template>

shallow ref

可以通过 shallow ref 来放弃深层响应性

  1. 减少大型不可变数据的响应性开销
  2. 与外部状态系统集成

DOM 更新时机

  • 当你修改了响应式状态时,DOM 会被自动更新。但是需要注意的是,DOM 更新不是同步的。
  • Vue 会在“next tick”更新周期中缓冲所有状态的修改,以确保不管你进行了多少次状态修改,每个组件都只会被更新一次。

要等待 DOM 更新完成后再执行额外的代码,可以使用 nextTick() 全局 API:

先来一个用于测试的Demo页:

<!DOCTYPE html>
<html>
 
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ref active toRef toRefs</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/3.1.1/vue.global.js"></script>
</head>
 
<body>
    <div id="app">
        <span> ref: </span>
        <input type="text" v-model="refValue" @input="inputRefHander" />
        {{ refValue }}
        <hr>
        <span> reactive: (显示age的值)</span>
        <input type="text" v-model="reactiveValue.age" @input="inputReactiveHander"  />
        {{ reactiveValue.age }}
        <hr>
        <span> toRef: (使age有响应)</span>
        <input type="text" v-model="toRefValue" @input="inputToRefValueHander" />
        {{ toRefValue}}
        <hr>
        <span> toRefs: </span>
        <input type="text" v-model="toRefsValue.name.value" @input="inputToRefsValueHander" />
        {{ toRefsValue.name.value}}
    </div>
</body>
 
</html>
<script>
const { createApp, reactive, toRefs, ref, toRef } = Vue;
const app = createApp({
    setup() {
        let name = 'chj';
        const obj = { name: 'chj', gender: 'male', age: 18 }
 
        const refValue = ref(name);               // => reactive({value:'chj'})
        const reactiveValue = reactive(obj)       // => reactive({ name: 'chj', gender: 'male', age: 18 })
        const toRefValue = toRef(obj, 'age')      // => reactive({value:18})
        const toRefsValue = toRefs(obj)           
 
        function inputRefHander() {
            console.log(`ref:::refValue:`);
            console.log(refValue);
            console.log(`原始数据`);
            console.log(name);
        }
 
        function inputReactiveHander() {
            console.log(`reactive:::reactiveValue`);
            console.log(reactiveValue);
            console.log(`原始数据`);
            console.log(obj);
        }
 
        function inputToRefValueHander() {
            console.log(`toRef:::toRefValue: `);
            console.log(toRefValue);
            console.log(`原始数据:`);
            console.log(obj);
        }
 
        function inputToRefsValueHander() {
            console.log(`toRefs:::toRefsValue: `);
            console.log(toRefsValue);
            console.log(`原始数据:`);
            console.log(obj);
        }
        return { refValue, inputRefHander, reactiveValue, inputReactiveHander, toRefValue, inputToRefValueHander, toRefsValue, inputToRefsValueHander }
    }
})
app.mount("#app")
</script>

随意输入内容,发现ref“包裹的”数据变化如下:

对于ref:
原始数据没有变化,而ref“包裹的数据”变成了一个新的对象,而且模板有变化。


对于reactive:
reactive处理的数据无论是原始数据,“包裹后的数据”,还是模板,都有变化。


对于toRef:

toRef处理的数据会有变化,而原始数据也有变化,但是模板没有变化


对于toRefs:wtoRefs处理的数据响应变化,原始数据也响应变化,但是模板并没有变化

总结

ef、isRef、toRef、toRefs、toRaw 看着一堆类似的东西,一个头两个大,今天整理一篇文章详细介绍它们的功能及区别。

1、ref

ref 属性除了能够获取元素外,也可以使用 ref 函数,创建一个响应式数据,当数据值发生改变时,视图自动更新。

<script lang="ts" setup>
import { ref } from 'vue'
let str: string = ref('我是张三')
const chang = () => {
  str.value = '我是钻石王老五'
  console.log(str.value)
}
</script>
<template>
  <div>
    {{ str }}
    <button type="button" @click="chang">修改值</button>
  </div>
</template>

2、isRef

检查变量是否为一个被 ref 包装过的对象,如果是返回 true ,否则返回 false。

import { ref, isRef, reactive } from 'vue'

let str: string = ref('我是张三')
let num: number = 1
let per = reactive({ name: '代码女神', work: '程序媛' })

console.log('strRes', isRef(str)) //true
console.log('numRes', isRef(num)) //false
console.log('perRes', isRef(per)) //false

3、toRef

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

toRef(obj, key) 将对象中的某个值转化为响应式数据,分为两种情况:

  • toRef 定义原始非响应式数据,修改值时,原始数据和 copy 数据都会变的,但是视图不更新。
<script>
  import { ref, isRef, toRef, reactive } from 'vue'
let obj = {
  name: '姓名',
  age: 18,
}
let name: string = toRef(obj, 'name')
const chang = () => {
  obj.name = '钻石王老五'
  name.value = '李四'
  console.log(obj.name) // 李四
  console.log('name', name) // 李四
}
//chang() //DOM挂载前调用
</script>
<template>
  <div>
    {{ obj.name }} ------- {{ name }}
    <button type="button" @click="chang">修改值</button>
  </div>
</template>

注意:如果是在 DOM 挂载之前调用 chang 方法,改变数值,此时数据和视图都会发生改变。

  • toRef 定义原始数据响应式数据,修改值时,原始数据,和 copy 数据都会改变,视图也会更新。
<script>
  import { ref, isRef, toRef, reactive } from 'vue'
let obj = reactive({
  name: '姓名',
  age: 18,
})
let name: string = toRef(obj, 'name')
const chang = () => {
  obj.name = '钻石王老五'
  name.value = '李四'
}
</script>
<template>
  <div>
    {{ obj.name }} ------- {{ name }}
    <button type="button" @click="chang">修改值</button>
  </div>
</template>

最终值为 “李四”。

4、toRefs

toRefs 用来解构 ref、reactive 包裹的响应式数据。接收一个对象作为参数,遍历对象上的所有属性,将对象上的所有属性变成响应式数据。

let obj = reactive({
  name: '姓名',
  age: 18,
})
let { name, age } = toRefs(obj)
const chang = () => {
  name.value = '钻石王老五'
  age.value++
}
</script>
<template>
  <div>
    {{ name }} ------- {{ age }}
    <button type="button" @click="chang">修改值</button>
  </div>
</template>

toRefs 解构数据时,如果某些参数作为可选参数,可选参数不存在时就会报错,如:

let obj = reactive({
  name: '姓名',
  age: 18,
})
let { name, age, work } = toRefs(obj)
const chang = () => {
  name.value = '钻石王老五'
  age.value++
  console.log('work', work.value)
  work.value = '程序媛'
}

此时可以使用 toRef 解决此问题,使用 toRef 解构对象某个属性时,先检查对象上是否存在该属性,如果存在就继承对象上的属性值,如果不存在就会创建一个。

修改上边的代码为:

let obj = reactive({
  name: '姓名',
  age: 18,
})
let { name, age } = toRefs(obj)
let work = toRef(obj, 'work')
const chang = () => {
  name.value = '钻石王老五'
  age.value++
  console.log('work', work.value)
  work.value = '程序媛'
}

5、toRaw

将响应式对象转为原始对象。做一些不想被监听的事情,从 ref 或 reactive 得到原始数据。

修改原响应式数据时,toRaw 转换得到的数据会被修改,视图也会更新,如:

<script lang="ts" setup>
import { ref, isRef, toRef, toRefs, reactive, toRaw } from 'vue'
let obj = reactive({
  name: '姓名',
  age: 18,
})
let newObj = toRaw(obj)
const chang = () => {
  obj.name = '钻石王老五'
  obj.age++
}
</script>
<template>
  <div>
    {{ obj.name }} ------- {{ obj.age }}
    <button type="button" @click="chang">修改值</button>
    <br />
    {{ newObj }}
  </div>
</template>

如果修改 toRaw 得到的原始数据,原数据也会被修改,但是视图不更新。如: