Vue3 新特性之响应式 API

1、ref

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

<script setup>
    const refValue = ref('refValue')
    console.log(refValue) // ref 对象
    console.log(refValue.value) // refValue
</script>
  • ref声明的变量,我们可以在 html代码中直接使用变量,但在 js 代码中,我们需要通过 .value来访问,因为 ref定义的变量返回的是一个响应式的 ref 对象,对象需要通过 .的形式来访问;
  • ref一般用来声明基本数据类型,当然也可以声明一个对象,但需要通过 .value.xxx来访问,比较繁琐,并且它将被 reactive函数处理为深层的响应式对象;
  • ref也可以用于获取 DOM,具体用法如下;
<script setup>
    const inputRef = ref(null)
    // setup 是包含了 beforeCreate 和 created,这个阶段还没挂载实例,所以需要在 mounted 中获取
    onMounted(() => {
      if (inputRef && inputRef.value) {
        inputRef.value.focus()
      }
    })
</script>
<template>
	<input type="text" ref="inputRef">
</template>

2、reactive

reactive是返回一个对象的响应式副本,即 proxy对象;

<script setup>
    const reactiveObj = { name: 'reactiveValue' }
    const reactiveValue = reactive(reactiveObj)
    console.log(reactiveValue, reactiveValue.name) // proxy 代理对象  reactiveValue
    
    const reactiveRef = ref(0)
    const reactiveRefValue = reactive(reactiveRef)
    reactiveRef.value++
    console.log(reactiveRef.value === reactiveRefValue.value) // true
    reactiveRefValue.value++
    console.log(reactiveRef.value === reactiveRefValue.value) // true
</script>
  • reactive声明的变量,返回的是一个 proxy对象,如果我们修改了变量,目标对象也会随之改变;
  • reactive一般用来声明复杂的对象类型,不能用来声明基本数据类型,如果声明了基本数据类型,它返回的是一个基本数据类型的副本,不是 proxy对象,修改它的值会报错;
  • 如果传入的值是一个 ref对象,reactive将解包所有深层的 refs,同时维持 ref 的响应性,也就是修改 reactive的值,对应的 ref对象也会随之变化;

3、readonly

readonly接受一个对象 (响应式或纯对象) 或 ref并返回原始对象的只读代理,只读代理是深层的:任何被访问的嵌套 属性也是只读的;

<script setup>
    const readObj = { count: 0 }
    const readRef = ref(0)
    const readReac = reactive({ count: 0 })
    const readRefValue1 = readonly(readObj)
    const readRefValue2 = readonly(readRef)
    const readRefValue3 = readonly(readReac)
    const updateReadOnly = () => {
        readObj.count++
        readRef.value++
        readReac.count++
        // 修改失败并会在控制台发出警告
        readRefValue1.count++
        readRefValue2.value++
        readRefValue3.count++
    }
</script>

4、shallowReactive

shallowReactive创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (暴露原始值)。

<script setup>
    const shallowObj = {
      name: 'shallowObj',
      age: 10,
      nested: {
        name: 'nested',
        bar: 2
      }
    }
    const shallowReactiveValue = shallowReactive(shallowObj)
    const updateShallowReactive = () => {
      // shallowReactiveValue.age++
      shallowReactiveValue.nested.bar++
    }
</script>
  • 当修改 shallowReactiveValue的外层属性,页面会跟着变化,也就是外层属性是响应式的,但是改变嵌套对象 nested时,页面不会更新,也就是这个对象不是响应式的;
  • 要是同时改变两个属性,则视图都会更新,因为响应式的属性会让视图更新,从而拿到了最新的值;
  • reactive不同,任何使用 ref的 property 都不会被代理自动解包;
  • 如果传入的对象是响应式的,那么它的深层嵌套属性也是响应式的;

结论:shallowReactive 与 reactive 不同的是它只会让外层的属性拥有响应式,嵌套对象(普通对象)的属性没有响应式;

5、shallowReadonly

shallowReadonly创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换 (暴露原始值)。

<script setup>
    // 传入普通对象
    const readOnlyObj = {
        age: 10,
        nested: {
            age: 2
        }
    }
    const shallowReadonlyValue0 = shallowReadonly(readOnlyObj)
    const updateShallowReadonly0 = () => {
        // 修改失败并会在控制台发出警告
        // shallowReadonlyValue.age++
        shallowReadonlyValue0.nested.age++
    }
    // 传入 reactive 声明的变量
    const readOnlyReactive = reactive({
        age: 10,
        nested: {
            age: 2
        }
    })
    const shallowReadonlyValue1 = shallowReadonly(readOnlyReactive)
    const updateShallowReadonly1 = () => {
        // 修改失败并会在控制台发出警告
        // shallowReadonlyValue.age++
        shallowReadonlyValue1.nested.age++
    }
</script>
  • 当传入的对象是普通对象,不能改变对象外层属性,可以改变嵌套对象的属性,但不是响应式的,目标对象会改变;
  • 当传入的对象是用 reactive对象时,不能改变对象外层属性,但是修改对象的嵌套属性仍然是响应式的,目标对象也会随之改变;
  • readonly不同,任何使用 ref的 property 都不会被代理自动解包;

6、shallowRef

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

<script setup>
    const shallowRefReac = {
        name: 'shallowRefReac',
        nested: {
            name: 'nested'
        }
    }
    const shallowRefValue = shallowRef(shallowRefReac)
    const updateShallowRefValue = () => {
        // 值会改变,但页面不会改变,说明 .value 的值不是一个响应式的属性 
        shallowRefValue.value.name += '+'
        // shallowRefValue.value.nested.name += '+'
        
        // shallowRefValue自身是响应式,修改 .value 页面会变化
        // shallowRefValue.value = '111'
        console.log(shallowRefValue) // RefImpl
    }
</script>

7、toRef

toRef可以用来为源响应式对象上的某个 property 新创建一个 ref,然后,ref可以被传递,它会保持对其源 property 的响应式连接;

<script setup>
    const toRefReac = reactive({
      name: 'toRef'
    })
    const toRefValue = toRef(toRefReac, 'name')
    const updateToRefValue = () => {
      toRefValue.value += '+'
      console.log(toRefValue.value === toRefReac.name) // true
    }
</script>
  • toRef第一个参数传入的目标对象需要是一个 reactive声明的变量,第二个参数是目标对象中的某个属性名,修改其值时,原目标对象对应的属性值也会随之改变,如果第二个参数是目标对象中没有的属性,则声明变量的值是 undefined
  • toRef的返回值是一个 ObjectRefImpl,与 ref不同,ref返回的是一个 RefImpl

8、toRefs

toRefs将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref

<script setup>
    const toRefsReac = reactive({
        name: 'toRefsReac',
        nested: {
            name: 'nested'
        }
    })
    const toRefsValue = toRefs(toRefsReac)
    console.log(toRefsValue) // { name: ObjectRefImpl, nested: ObjectRefImpl }
    const updateToRefsValue = () => {
        toRefsValue.name.value += '+'
        console.log(toRefsValue.name.value === toRefsReac.name) // true
    }
</script>
  • toRefs接收的是一个 reactive的值,它会返回一个普通对象,对象的每个属性是一个 ObjectRefImpl值,指向目标对象相应的属性;
  • 当从组合式函数返回响应式对象时,toRefs非常有用,这样消费组件就可以在不丢失响应性的情况下对返回的对象进行解构/展开;
<script setup>
    // const updateUseState = () => {
    //   name += '+'
    //   age += 1
    //    // 值变化了,但是页面不会改变,说明解构出来的变量是不具有响应式的
    //   console.log(name, age)
    // }
    // function useState() {
    //   const state = reactive({
    //     name: 'stateName',
    //     age: 10
    //   })
    //   return state
    // }
    
    // 使用 toRefs
    let { name, age } = useState()
    const updateUseState = () => {
        name.value += '+'
        age.value += 1
        // 值变化了,页面也会随之改变
        console.log(name, age) 
    }
    function useState() {
        const state = reactive({
            name: 'stateName',
            age: 10
        })
        return toRefs(state)
    }
</script>

9、computed

computed计算属性在 Vue3 中有两种声明方式,一种是接受一个 getter函数,并根据 getter的返回值返回一个不可变的响应式 ref对象;另一种是接受一个具有 getset函数的对象,用来创建可写的 ref对象;

<script setup>
    // 方式一
    const reactiveValue1 = reactive({
        name: 'reactiveValue1',
        age: 10
    })
    const computedValue1 = computed(() => reactiveValue1.age + 1)
    const updateComputed1 = () => {
        // 修改失败,并在控制台发出警告
        computedValue1.value++
        console.log(computedValue1) // ComputedRefImpl
    }
    
    // 方式二
    const reactiveValue2 = reactive({
        name: 'reactiveValue2',
        age: 10
    })
    const computedValue2 = computed({
        get: () => reactiveValue2.age + 1,
        set: (val) => reactiveValue2.age = val - 1
    })
    const updateReactive2 = () => {
        reactiveValue2.age++
    }
    const updateComputed2 = () => {
        // 修改成功,页面也随之变化
        computedValue2.value++
        console.log(computedValue2) // ComputedRefImpl
    }
</script>

在 Vue3 中,computed声明变量是通过 API 的形式来声明,分别两种形式,一种是 getter,一种是有 getset的对象,同时,computed的 API 还可以传入第二个参数 debuggerOptions,它是一个对象,包含两个方法 onTrackonTrigger,他们可以用来调试;

10、watch

watchAPI 与选项式 API this.$watch 完全等效,watch需要侦听特定的数据源,并在单独的回调函数中执行副作用,默认情况下,它是惰性的——即回调仅在侦听源发生变化时被调用;

(1)侦听单一数据源

<script setup>
    const watchReac = reactive({
        name: 'watchReac',
        nested: {
            name: 'nested',
            hobbies: ['篮球', '游戏']
        }
    })
    const watchValue = ref<any>('')
    // 侦听对象某一属性
    watch(() => watchReac.name, (val, preVal) => {
        console.log("watch: ", val, preVal)
        watchValue.value = val
    })
    // 侦听对象的嵌套对象
    // watch(() => watchReac.nested, (val, preVal) => {
    //    // 不会执行,需要传入第三个参数,{ deep: true }
    //    console.log("watch: ", val, preVal)
    //    watchValue.value = val
    // })
    // 侦听整个对象
    // watch(watchReac, (val, preVal) => {
    //    console.log("watch: ", val, preVal)
    //    watchValue.value = val
    // })
    const updateWatchValue = () => {
        watchReac.nested.name += '+'
    }
</script>

侦听单一数据源时,传入的参数有侦听对象、回调函数、配置选项;

  • 侦听对象:两种形式,一种是箭头函数返回一个对象,另一种是直接传入侦听对象;
  • 回调函数:两个参数,分别是修改后的值和修改前的值;
  • 配置选项:deep是否深入侦听,immediate是否立即执行;

(2)侦听多个数据源

<script setup>
    const watchReac = reactive({
        name: 'watchReac',
        nested: {
            name: 'nested',
            hobbies: ['篮球', '游戏']
        }
    })
    const watchRef = ref(0)
    const watchReacValue = ref<any>('')
    const watchRefValue = ref<number>()
    watch([watchReac, watchRef], ([reacVal, refVal], [preReacVal, preRefVal]) => {
        watchReacValue.value = reacVal
        watchRefValue.value = refVal
    })
    const updateWatchValue = () => {
        watchReac.nested.name += '+'
        watchRef.value++
    }
</script>

侦听多个多数据源时,传入的参数有侦听对象、回调函数、配置选项;

  • 侦听对象:需要传入一个数组对象,数组对象包括要侦听的数据;
  • 回调函数:两个参数,参数分别为数组类型,数组的值分别是侦听对象修改前后的值,顺序按照侦听对象的顺序;
  • 配置选项:deep是否深入侦听,immediate是否立即执行;

11、watchEffect

为了根据响应式状态自动应用和重新应用副作用,我们可以使用 watchEffect函数,它立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数;

<script setup>
    const watchEffectRef = ref(0)
    const watchEffectReac = reactive({
        count: 0
    })
    watchEffect(() => {
        console.log("watchEffect: ", watchEffectRef.value)
    })
    const updateWatchEffect = () => {
        watchEffectReac.count++
        // watchEffectRef.value++
    }
</script>
  • watchEffect会立即执行一次回调函数,之后它追踪的依赖发生变化才会执行,也就是说,当我们改变 watchEffectReac的值时,并不会执行 watchEffect的回调;
  • watchEffect可以看作是配置了 immediate参数的 watch
  • watchEffect默认是在组件更新前执行,可以通过配置 flush选项来设置执行时机;
  • watchEffect可以传入第二个参数,包括配置项 flushonTrackonTrigger
    • flush:有三个值 pre(默认值)、postsync
      • pre:默认值,在组件更新前执行;
      • post:在组件更新后执行;
      • sync: 强制效果始终同步触发;
    • onTrack: 响应式 property 或 ref 作为依赖项被追踪时被调用;
    • onTrigger: 依赖项变更导致副作用被触发时被调用;

Vue3 中还提供了 watchPostEffectwatchSyncEffect两个 API,这两个是 watchEffect的别名,只是在 watcher Effect的基础上配置了 flush的值,分别为 postsync

猜你喜欢

转载自blog.csdn.net/Ljwen_/article/details/124987865