【Vue3】refs (持续更新)

ref

接受一个值,返回一个响应式并且可变的 ref 对象。ref 对象具有指向值的单个属性的值。
ref数据发生改变,界面会自动更新。

实例

<template>
  <div class="container">
    {{ count }}
    <br>
    <button @click="add">
      点击
    </button>
  </div>
</template>

<script lang="ts" setup>
// import { ref, reactive } from 'vue'
import { ref } from 'vue'
const data: number = 0
const count = ref(data)

function add () {
  count.value++
}

console.log('----')
console.log(count)
console.log(count.value.constructor)

// interface OriginObj {
//   val: number
// }
// const origin:OriginObj = { val: 0 }// 原始数据为对象
// const count = ref(origin)
// // const count = reactive(origin)

// function add () {
//   count.value.val++
// }

// console.log('----')
// console.log(count)
// console.log(count.value.constructor)
</script>
<style scoped lang="less">
.container {
    background: #fff;
    width: 100%;
    height: 500px;
    margin: 20px 20px;
}
</style>

传递的原始数据orgin可以是原始值也可以是引用值,但是需要注意,如果传递的是原始值,指向原始数据的那个值保存在返回的响应式数据的.value中,如上count.value;如果传递的一个对象,返回的响应式数据的.value中对应有指向原始数据的属性,如上count.value.val

我们看下打印的结果:

console.log(count)
console.log(count.constructor)

首先,如果传入的是原始值数据,返回的结果如下:

然后,如果传入的是对象,返回结果如下:

对比发现,不管传递数据类型的数据给ref,无论是原始值还是引用值,返回的响应式数据对象本质都是由RefImpl类构造出来的对象。但不同的是里头的value,一个是原始值,一个是Proxy对象。

RefImpl源码

class RefImpl<T> {
  private _value: T
  private _rawValue: T

  public dep?: Dep = undefined
  public readonly __v_isRef = true

  constructor(value: T, public readonly _shallow: boolean) {
    this._rawValue = _shallow ? value : toRaw(value)
    this._value = _shallow ? value : toReactive(value)
  }

  get value() {
    trackRefValue(this)
    return this._value
  }

  set value(newVal) {
    newVal = this._shallow ? newVal : toRaw(newVal)
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = this._shallow ? newVal : toReactive(newVal)
      triggerRefValue(this, newVal)
    }
  }
}

可以看见RefImpl class传递了一个泛型类型T,里头具体包含:

有个私有属性_value, _rawValue,类型为T,有个公开只读属性__v_isRef值为true,有个dep属性
有两个方法,get value(){}和set value(){},分别对应私有属性的读写操作,用于供外界操作value
有一个构造函数constructor,用于构造对象。构造函数接受两个参数:
第一个参数value,要求是T类型
第二个参数_shallow,默认值为true

总结:

  1. ref本质是将一个数据变成一个对象,这个对象具有响应式特点
  2. ref接受的原始数据可以是原始值也可以是引用值,返回的对象本质都是RefImpl类的实例
  3. 无论传入的原始数据时什么类型,当原始数据发生改变时,并不会影响响应数据,更不会触发UI的更新。但当响应式数据发生改变,对应界面UI是会自动更新的,注意不影响原始数据。所以ref中,原始数据和经过ref包装后的响应式数据是无关联的

isRef

检查值是否为一个 ref 对象。

unref

如果参数为 ref,则返回内部值,否则返回参数本身,这是 val = isRef(val) ? val.value : val

实例

<template>
    <p>{{count}}</p>
</template>
<script setup>
import { ref, isRef, unref } from 'vue'
const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1
console.log('-------')
const info = ref({name :'名字',info:{age:18,height:1111,}})
const infos = {name :'名字',info:{age:18,height:1111, }}
console.log('isRef:')
console.log(isRef(info))
console.log('unref info:')
console.log(unref(info))
console.log('unref infos:')
console.log(unref(infos))

</script>

toRef

可以用来为源响应式对象上的某个 property 新创建一个 ref。然后,ref 可以被传递,它会保持对其源 property 的响应式连接。
toRef接收两个参数,第一个参数是哪个对象,第二个参数是对象的哪个属性。
toRef当数据发生改变是,界面不会自动更新。

实例

<template>
    <div>
        <button @click="change">点击更新obj</button>
        <span>obj.name: {{obj.name}} ,   </span>
        <span>newObj: {{newObj}}</span>
    </div>
</template>
<script setup>
import { toRef } from 'vue'

let obj = {name : 'alice', age : 12};
// let newObj= ref(obj.name);
let newObj= toRef(obj, 'name');

const change = () => {
    newObj.value = 'Tom';
    console.log('obj');
    console.log(obj);
    console.log('newObj');
    console.log(newObj);
}


</script>

toRefs

有的时候,我们希望将对象的多个属性都变成响应式数据,并且要求响应式数据和原始数据关联,并且更新响应式数据的时候不更新界面,就可以使用toRefs,用于批量设置多个数据为响应式数据。

实例

<template>
    <div>
        <button @click="change">点击更新obj</button>
        <span>obj.name: {{obj.name}} ,   </span>
        <span>newObj.name: {{newObj.name}}</span>,
        <span>newObj.age: {{newObj.age}}</span>,
    </div>
</template>
<script setup>
import { toRefs } from 'vue'

let obj = {name : 'alice', age : 12};
let newObj = toRefs(obj)
const change = () => {
    newObj.name.value = 'Tom';
    newObj.age.value = 30;
    console.log('obj');
    console.log(obj);
    console.log('newObj');
    console.log(newObj);
}

</script>

customRef

创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收 track 和 trigger 函数作为参数,并且应该返回一个带有 get 和 set 的对象。

<template>
<div>
  customRef的例子:
  <span>{{obj}}</span>
  <button @click="inc">button</button>
</div>
</template>

<script>
import { customRef } from 'vue';

// customRef用于 自定义ref
// 自定义 ref 需要提供参数传值
function myRef(value) {
    // 自定义 ref 需要提供 customerRef 返回值
    // customer 需要提供一个函数作为参数
    // 该函数默认带参数 track 和 trigger ,都是方法。
    return customRef((track, trigger) => {
      return {
        // customer 需要提供一个对象 作为返回值
        // 该对象需要包含 get 和 set 方法。
        get() {
          // track 方法放在 get 中,用于提示这个数据是需要追踪变化的
          track();
          console.log('get', value);
        
          return value;
        },
        // set 传入一个值作为新值,通常用于取代 value
        set(newValue) {
          console.log('set', newValue);
          value = newValue;
          // 记得触发事件 trigger,告诉vue触发页面更新
          trigger();
        }
      }
    })
}

export default {
  name: 'App',
  setup() {
    const obj = myRef(123);
    function inc() {
      obj.value += 1;
    }

    return {
      obj,
      inc
    };
  }
}
</script>

假如我们去掉了 track 和 trigger ,那么将失去视图层追踪变化的能力(可以显示控制)。如果需要进行视图层追踪,请注意在 set 中 value 发生变化后即刻执行 trigger。

shallowRef,triggerRef

创建一个跟踪自身 .value 变化的 ref,但不会使其值也变成响应式的。
shallowRef生成的数据只有第一层key是响应式的,因为ref数据有一个特点,会默认加一个value属性,传入的对象默认会作为value的属性值,所有用shallowRef生成的响应式数据只有value是响应的,是能达到响应式效果的。如果想将页面UI改变,我们可以用triggerRef。
triggerRef: 手动执行与 shallowRef 关联的任何作用 (effect)。

<template>
    <div>
        shallowRef的例子:
        <span>refNum:{{ refNum.age }}, </span>
        <span>shallowRefNum:{{ shallowRefNum.age }}, </span>
        <button  @click="change">点击数字加1</button>
    </div>
   
</template>
<script setup>
import { ref, shallowRef, triggerRef } from 'vue'
let refNum  =ref({age:18})
let shallowRefNum = shallowRef({age:18})

const change = () => {
    // refNum.value.age ++;
    shallowRefNum.value.age++
    triggerRef(shallowRefNum)

}
</script>

posted @ 2021-09-29 16:13  攀登高山  阅读(874)  评论(0编辑  收藏  举报