【Vue3学习笔记】响应式:核心

392 阅读4分钟

响应式数据:Ref、Reactive

<script setup>
import { ref, reactive } from 'vue';
// ref 基础类型数据
const name = ref('高启强');
// reactive 引用类型数据object, array, function
const obj = reactive({
  name: '高启盛',
  age: 28,
});
// ref数据在js中需要使用.value访问
console.log(name.value)
// reactive数据不用.value
console.log(obj.name)
</script>

<template>
  <div>
    <p>{{ name }}</p>
    <p>{{ obj.name }}</p>
  </div>
</template>

ref和reactive的区别

  • ref数据在js中需要使用.value访问,reactive数据不用.value
  • ref创建基础类型数据, 如string,number,boolean。 reactive创建引用类型数据object, array, function

只读数据:readonly

readonly的作用顾名思义就是不允许修改,通过readonly包装的数据是不可修改的,有些像const。如果要是尝试修改也是无效的,并且控制台会有警告信息。

readonly适用于数据传给别的地方但不允许修改数据,只能在当前逻辑修改的情况。比如通过readonly将一个ref数据包裹,将包裹后的readonly数据返回到其他地方用,但是别的地方无法修改。要修改只能更改原ref数据,ref数据改了readonly的数据也会跟着改

<script>
import { ref, readonly } from 'vue'
export default {
  setup() {
    // 响应式数据
    const nameData = ref('高启强')
    // 不可修改 (上面的ref数据变了,这个readonly数据会跟着一起变)
    const name = readonly(nameData)

    // 修改控制台有警告:[Vue warn] Set operation on key "value" failed: target is readonly.
    name.value = '高启盛'

    console.log(name.value);  // 这里打印的还是:高启强
    return {
      name
    }
  }
}
</script>

readonly和const有什么不同?

  • readonly可以直接修改整个对象, 不可以修改其中的属性。
    <script>
    import { reactive, readonly } from 'vue'
    export default {
      setup() {
         let obj = reactive({
            name: '高启强',
         })
         let person1 = readonly(obj)

         // readonly不能修改其中的某个属性
         person1.name = '高启盛'
         console.log(person1.name);  // 高启强(修改失败)
         // readonly可以修改整个对象
         person1 = {
          name: '高启盛2号'
         }
         console.log(person1.name);  // 高启盛2号 (修改成功)
      }
    }
    </script>
  • const不能直接修改整个对象,可以修改其中的属性。

    <script>
    export default {
      setup() {
         const person1 = {
            name: '高启强',
         }
         // const可以修改其中的属性
         person1.name = '高启盛'
         console.log(person1.name);  // 高启盛(修改成功)
         // const不可以修改整个对象,并且控制台报错,下面的console也不执行了
         person1 = {
          name: '高启盛2号'
         }
         console.log(person1.name);  // 高启盛2号 (修改失败)
      }
    }
    </script>

计算属性:computed

  • 传入函数:computed是一个方法,一般使用就是传入一个回调函数并返回计算的结果。computed方法会得到最终的结果。
    <script setup>
    import { ref, computed } from 'vue';

    const num = ref(2);
    const price = ref(12);

    const totalPrice = computed(() => num.value * price.value);

    console.log(totalPrice); // 24
    </script>
  • 传入对象:computed也可以传入对象,对象中有set和get方法,在set的时候可以做一些其他的处理。

    <script setup>
    import { ref, computed } from 'vue';

    const num = ref(2);
    const price = ref(12);

    // let totalPrice = computed(() => num.value * price.value);
    let totalPrice = computed({
      get: () => num.value * price.value,
      set: (val) => {
        // 设置totalPrice的时候改变num的值
        num.value += val;
        return val + 1;
      },
    });
    // 设置totalPrice,num会根据totalPrice设置的值改变
    totalPrice.value = 33;
    console.log(num); // 35
    </script>

修改totalPrice的时候改变num的值,修改totalPrice为33,num会根据totalPrice设置的值改变。

数据监听:watch

watch的基础用法

watch用来监听数据的变化,跟vue2中的作用一样,只是写法不一样。

需要注意的是监听一定要放到前面,修改数据放到监听的后面,这样才能监听到。如果需要监听前面的修改需要开启deep深度监听

    <script>
    import { ref, watch } from 'vue'
    export default {
      setup() {
        const name = ref('高启强')

        // 要先监听后修改,在这里修改数据写在监听前面是监听不到的,需开启immediate立即触发才可以监听到
        name.value = '高启强1号'
        // 开始监听
        watch(name, (newValue, oldValue) => {
          // 上面的修改没有监听到,监听到了下面的修改
          console.log(newValue, oldValue);  // 高启强2号, 高启强1号
        })

        name.value = '高启强2号'

      }
    }
    </script>

watch的配置参数

一般watch不做其他配置的话用前两个参数就够了,第一个是监听的数据,第二个是数据改变时的回调函数,如果需要修改watch配置参数就需要第三个参数了,第三个参数是一个对象,就是一些配置属性。比如常用的deep深度监听、**immediate**立即触发配置。

深度监听

在第三个参数中传入deep:true选项就可以深度监听对象,就是对象里面的对象也可以监听。

立即触发

在第三个参数中传入immediate:true选项就可以在第一次数据创建的时候就开始监听

    <script>
    import { ref, watch } from 'vue'
    export default {
      setup() {
        const name = ref('高启强')

        watch(name, (newValue, oldValue) => {
          // 上面的修改没有监听到,监听到了下面的修改
          console.log(newValue, oldValue);  // 高启强2号, 高启强1号
        }, {
          deep: true,
          immediate: true
        })

        name.value = '高启强1号'
      }
    }
    </script>

控制台打印结果:

高启强 undefined

高启强1号 高启强

watch监听多个

watch第一个参数最常用的就是直接传入一个数据进行监听,其实第一个参数还可以传数组和函数,传数组代表监听多个,传函数可以只监听reactive中的某个属性。

监听多个:传入数组监听多个数据,回调函数中返回的newValue和oldValue也会是一个数组,和传入的顺序一致。

    <script>
    import { reactive, watch } from 'vue'
    export default {
      setup() {
        const obj = reactive({
          name: '张三',
          age: 35
        })
        // 监听reactive数据其中的一个属性,这里监听name和age两个数据
        watch([
          () => obj.name,
          () => obj.age
        ], ([newName, newAge], [oldName, oldAge]) => {
          console.log(newName, oldName);  // 李四 张三
          console.log(newAge, oldAge);  // 42 35
        })
        obj.name = '李四'
        obj.age = 42
      }
    }
    </script>

watch停止监听

watch方法会返回一个可执行的方法,执行这个方法就可以取消监听

    const stop = watch(source, callback)

    // 当已不再需要该侦听器时:
    stop()

watchEffect()

watchEffect和watch的作用都是监听数据,不同的是watchEffect不需要指定监听谁,第一个参数直接传入监听回调函数,会根据函数中用到的数据进行自动监听,只要其中任何一个数据变了就会执行。

    <script>
    import { reactive, watchEffect } from 'vue'
    export default {
      setup() {
        const obj = reactive({
          name: '张三',
          age: 35
        })

        watchEffect(() => {
          console.log(obj.name); // 张三
        })
        // 已经开始监听obj.nam, 只要obj.nam改变了就会执行watchEffect函数中的内容
      }
    }
    </script>

副作用清理、取消监听

watchEffect的回调参数有一个参数,就是取消副作用的方法。

当在watchEffect里面执行了一个方法后需要取消或者关闭的时候可以使用清除副作用方法,比如在watchEffect里面执行了一个setInterval定时器,或者通过window.addEventListener订阅了一个事件,这些最后都是需要关闭或者取消的。所以就可以通过取消副作用方法进行清除方法。

取消副作用方法的执行时机

1.当组件被销毁的时候会执行

2.当取消监听的时候会执行

    <script>
    import { watchEffect } from 'vue'
    export default {
      setup() {
        const stop = watchEffect((onCleanup) => {
          const timer = setInterval(() => {
            console.log('a')
          }, 1000)
          // 当组件被销毁的时候该函数执行
          // 当取消监听的时候会执行
          onCleanup(() => {
            clearInterval(timer)
          })
        })
        // 取消监听 会执行onCleanup方法
        stop()
      }
    }
    </script>

修改执行时机

第二个参数中的flush属性用来更改执行时机,更改这个属性可以修改是在组件更行前执行还是组件更新后执行。

  • flush:'pre'在组件更新前执行 默认为'pre'
  • flush:'post'在组件更新后执行
  • flush:'sync强制效果始终同步触发。然而,这是低效的,应该很少需要。

    <script>
    import { ref, watchEffect, onBeforeUpdate } from 'vue'
    export default {
      setup() {
        const name = ref('张三')

        onBeforeUpdate(() => {
          console.log('onBeforeUpdate');
        })

        watchEffect(() => {
          console.log('watchEffect');
          console.log(name.value);
        }, {
          //'pre' 在组件更新前运行,默认为'pre'
          //'post'在组件更新后运行
          //'sync'强制效果始终同步触发。然而,这是低效的,应该很少需要。
          flush:'post'
        })

        function changeName() {
          name.value = '李四'
          console.log('修改数据');
        }
        return {
          name,
          changeName
        }
      }
    }
    </script>
    <template>
      <div>{{ name }}</div>
      <button @click='changeName'>按钮</button>
    </template>

点击按钮会执行changeName事件,然后组件就会更新

flush:pre 执行结果:

修改数据 watchEffect 李四 onBeforeUpdate

flush: post 执行结果

修改数据 onBeforeUpdate watchEffect 李四

watchPostEffect()

[watchEffect()](<https://cn.vuejs.org/api/reactivity-core.html#watcheffect>) 使用 flush: 'post' 选项时的别名。

watchSyncEffect()

[watchEffect()](<https://cn.vuejs.org/api/reactivity-core.html#watcheffect>) 使用 flush: 'sync' 选项时的别名。