Vue3+TS系统学习十一 - 详解Vue3 Composition API(二上)

简介: 前面给大家分享了Options API语法中代码的复用、Options API编码的优缺点,以及setup函数,响应式API等,这次将给大家分享Vue3 Composition API中的计算属性,侦听器,生命周期函数,Provide和Inject等。1.1 computed

1.1 computed


在前面我们讲解过计算属性computed:当我们的某些属性是依赖其他状态时,我们可以使用计算属性来处理

  • 在前面的Options API中,我们是使用computed选项来完成。
  • 在Composition API中,我们可以在 setup 函数中使用computed函数来编写一个计算属性。

如何使用computed函数呢?

  • 方式一:接收一个getter函数,并根据 getter 的返回值返回一个不可变的响应式 ref 对象。
  • 方式二:接收一个具有 get 和 set 方法的对象,返回一个可变的(可读写)ref 对象。


1.1.1 computed基本使用


下面我们来看看computed函数的基本使用:接收一个getter函数。

首先使用Vue CLI新建一个01_composition_api的Vue3项目,然后在01_composition_api项目的src目录下新建07_computed使用文件夹,然后在该文件夹下分别新建:App.vue,ComputedAPI.vue组件。

ComputedAPI.vue子组件,代码如下所示:

<template>
  <div>
    <!-- 2.使用fullName计算属性 -->  
    <h4>{{fullName}}</h4>
    <button @click="changeName">修改firstName</button>
  </div>
</template>
<script>
  import { ref, computed } from 'vue';
  export default {
    setup() {
      const firstName = ref("Kobe");
      const lastName = ref("Bryant");
      // 1.用法一: 传入一个getter函数。computed的返回值是一个ref对象
      const fullName = computed(() => firstName.value + " " + lastName.value);
      const changeName = () => {
        // 3.修改firstName
        firstName.value = "James"
      }
      return {
        fullName,
        changeName
      }
    }
  }
</script>

可以看到,我们使用了computed函数来定义了一个fullName计算属性,其中computed函数需要接收一个getter函数,我们在getter函数中对响应式的数据进行计算和返回。

App.vue根组件,代码如下所示(省略了组件注册的代码):

<template>
  <div class="app" style="border:1px solid #ddd;margin:4px">
    App组件
    <ComputedAPI></ComputedAPI>
  </div>
</template>
.....

然后我们修改main.js程序入口文件,将导入的App组件改为07_computed使用/App.vue路径下的App组件。

保存代码,运行在浏览器的效果,如图10-16所示。计算属性可以正常显示,当点击修改firstName按钮时也可以响应式刷新页面。

image.png

        图10-16  computed函数的基本使用


1.1.2 计算属性get和set方法


接着我们再来看看computed函数的get和set方法的使用:接收一个对象,里面包含 setget方法。

修改ComputedAPI.vue子组件,代码如下所示:

......
<script>
  import { ref, computed } from 'vue';
  export default {
    setup() {
      const firstName = ref("Kobe");
      const lastName = ref("Bryant");
      // const fullName = computed(() => firstName.value + " " + lastName.value);
      // 1.用法二: 传入一个对象, 对象包含getter/setter
      const fullName = computed({
        get: () => firstName.value + " " + lastName.value, // getter 方法
        set(newValue) { // setter 方法
          const names = newValue.split(" ");
          firstName.value = names[0];
          lastName.value = names[1];
        }
      });
      const changeName = () => {
        // firstName.value = "James"
        // 3.修改fullName计算属性
        fullName.value = "James Bryant";
      }
      return {
        fullName,
        changeName
      }
    }
  }
</script>

可以看到,我们使用了computed函数来定义了一个fullName计算属性,其中computed函数接收一个具有 get 和 set 方法的对象,我们在get方法中对响应式的数据进行计算和返回,在set方法中对传入的新值重新赋值给firstName和lastName响应式对象的值。

保存代码,运行在浏览器后。fullName计算属性可以正常显示,当点击修改firstName按钮时也可以响应式刷新页面。


2.1 watchEffect侦听


在前面的Options API中,我们可以通过watch选项来侦听data,props或者computed的数据变化,当数据变化时执行某一些操作。

在Composition API中,我们可以使用watchEffectwatch来完成响应式数据的侦听。

  • watchEffect用于自动收集响应式数据的依赖。
  • watch需要手动指定侦听的数据源。

下面我们先来看看watchEffect函数的基本使用。


2.1.1 watchEffect基本使用


当侦听到某些响应式数据变化时,我们希望执行某些操作,这个时候可以使用 watchEffect

  • 首先,watchEffect传入的函数会被立即执行一次,并且在执行的过程中会收集依赖。
  • 其次,只有收集的依赖发生变化时,watchEffect传入的函数才会再次执行。

下面通过一个案例来学习watchEffect基本使用。我们在01_composition_api项目的src目录下新建08_watch使用文件夹,然后在该文件夹下分别新建:App.vue,WatchEffectAPI.vue组件。

WatchEffectAPI.vue子组件,代码如下所示:

<template>
  <div>
    <h4>{{age}}</h4>
    <button @click="changeAge">修改age</button>
  </div>
</template>
<script>
  import { ref, watchEffect } from 'vue';
  export default {
    setup() {
      const age = ref(18);
      // watchEffect: 1.自动收集响应式的依赖 2.默认会先执行一次 3.获取不到新值和旧值
      watchEffect(() => {
        console.log("age:", age.value); // 侦听age的改变, age发生变化后会再次执行
      });
      const changeAge = () => age.value++
      return {
        age,
        changeAge
      }
    }
  }
</script>

可以看到,我们在setup函数中调用了watchEffect函数,并给该函数传递了一个回调函数,传入的回调函数会被立即执行一次,并且在执行的过程中会收集依赖(收集age的依赖)。当收集的依赖发生变化时,watchEffect传入的回调函数又会再次执行。

App.vue根组件,代码如下所示:

<template>
  <div class="app" style="border:1px solid #ddd;margin:4px">
    App组件
    <WatchEffectAPI></WatchEffectAPI>
  </div>
</template>
.....

然后我们修改main.js程序入口文件,将导入的App组件改为08_watch使用/App.vue路径下的App组件。

保存代码,运行在浏览器的效果,如图10-17所示。可以看到,默认会先执行一次打印age:18,当点击修改age按钮来改变age时,watchEffect侦听到age发生改变后,回调函数又会再次执行,并打印age:19。

image.png

                              图10-17  watchEffect基本使用


2.1.2 watchEffect停止侦听


如果在发生某些情况下,我们希望停止侦听,这个时候我们可以获取watchEffect的返回值函数,调用该函数即可。

比如在上面的案例中,我们age达到20的时候就停止侦听,WatchEffectAPI.vue子组件,代码如下所示:

....
<script>
  import { ref, watchEffect } from 'vue';
  export default {
    setup() {
      const age = ref(18);
      // 1.stop是watchEffect返回值的函数,用来停止侦听
      const stop = watchEffect(() => {
        console.log("age:", age.value); // 侦听age的改变
      });
      const changeAge = () => {
          age.value++
          if (age.value > 20) {
            stop(); // 2.停止侦听age的变化
          }
      }
      return {age, changeAge}
    }
  }
</script>

保存代码,运行在浏览器后,可以看到默认会先执行一次打印age:18,当点击修改age按钮来改变age时,当age大于20的时候,由于调用了watchEffect返回的stop函数,watchEffect将会取消对age变量的侦听。


2.1.3 watchEffect清除副作用


什么是清除副作用呢?

  • 比如在开发中我们需要在侦听函数中执行网络请求,但是在网络请求还没有达到的时候,我们停止了侦听器,或者侦听器侦听函数被再次执行了。
  • 那么上一次的网络请求应该被取消掉(类似前面讲的防抖),这个时候我们就可以清除上一次的副作用。

在我们给watchEffect传入的函数被回调时,其实可以获取到一个参数:onInvalidate

  • 副作用即将再次重新执行 或者 侦听器被停止 时会执行onInvalidate函数传入的回调函数。
  • 我们可以在传入的回调函数中,执行一些清除的工作。

我们在08_watch使用文件夹下新建:WatchEffectAPIClear.vue组件。

WatchEffectAPIClear.vue子组件,代码如下所示(省略的template和上面案例一样):

......
<script>
  import { ref, watchEffect } from 'vue';
  export default {
    setup() {
      const age = ref(18);
      watchEffect((onInvalidate) => {
        const timer = setTimeout(() => {
          console.log("模拟网络请求,网络请求成功~");
        }, 2000)
        onInvalidate(() => {
          // 当侦听到age发生变化和侦听停止时会执行该这里代码,并在该函数中清除额外的副作用
          clearTimeout(timer); // age发生改变时,优先清除上一次定时器的副作用
          console.log("onInvalidate");
        })
        console.log("age:", age.value); // 侦听age的改变
      });
      const changeAge = () => age.value++
      return {age,changeAge}
    }
  }
</script>

可以看到,watchEffect函数传入的回调函数接收一个onInvalidate参数,onInvalidate也是一个函数,并且该函数也需要接收一个回调函数作为参数。

App.vue根组件,代码如下所示:

<template>
  <div class="app" style="border:1px solid #ddd;margin:4px">
    App组件
    <!-- <WatchEffectAPI></WatchEffectAPI> -->
    <WatchEffectAPIClear></WatchEffectAPIClear>
  </div>
</template>

保存代码,运行在浏览器的效果,如图10-18所示。刷新页面,立马连续点击3次修改age,我们可以看到watchEffect函数侦听到age改变了3次,并在每次将重新执行watchEffect函数的回调函数时先执行了onInvalidate函数中的回调函数来清除副作用(即把上一次的定时器给清除了,所以只有最后一次的定时器没有被清除)。

image.png

                                          图10-18  watchEffect清除副作用


2.1.4 watchEffect执行时机


在讲解 watchEffect执行时机之前,我们先补充一个知识:在setup中如何使用ref或者元素或者组件?

  • 其实非常简单,我们只需要定义一个前面讲的ref对象,绑定到元素或者组件的ref属性上即可。

我们在08_watch使用文件夹下新建:WatchEffectAPIFlush.vue组件。

WatchEffectAPIFlush.vue子组件,代码如下所示(省略的template和上面案例一样):

<template>
  <div>
    <h4 ref="titleRef">哈哈哈</h4>
  </div>
</template>
<script>
  import { ref, watchEffect } from 'vue';
  export default {
    setup() {
      // 1.定义一个titleRef来拿到h4元素的DOM对象(组件对象也是一样)
      const titleRef = ref(null);
      // 2.h4元素挂载完成之后会自动赋值到titleRef变量上,这里监听titleRef变量被赋值,并打印出来看
      watchEffect(() => {
        console.log(titleRef.value); // 3.打印h4元素的DOM对象
      })
      return { titleRef }
    }
  }
</script>

可以看到,我们先用ref函数定义了一个titleRef响应式变量,接着该变量在setup函数中返回,并绑定到h4元素的ref属性上(注意:不需要用v-bind指令来绑定)。当h4元素挂载完成之后会自动赋值到titleRef变量上。为了观察titleRef变量被赋值,这里我们使用watchEffect函数来侦听titleRef变量的改变,并打印出来。最后我们在App.vue根组件中导入和使用WatchEffectAPIFlush组件(和前面的操作基本一样,这里不再贴代码)。

保存代码,运行在浏览器的效果,如图10-19所示。刷新页面,我们会发现打印结果打印了两次。

  • 这是因为setup函数在执行时就会立即执行传入的副作用函数(watchEffect的回调函数),这个时候DOM并没有挂载,所以打印为null。
  • 而当DOM挂载时,会给titleRef变量的ref对象赋值新的值,副作用函数会再次执行,打印出对应的元素。

image.png

                                      图10-19  ref获取元素对象

如果我们希望在第一次的时候就打印出来对应的元素呢?

  • 这个时候我们需要改变副作用函数的执行时机。
  • 它的默认值是pre,它会在元素 挂载 或者 更新 之前执行。
  • 所以我们会先打印出来一个空的,当依赖的titleRef发生改变时,就会再次执行一次,打印出元素。

我们可以设置副作用函数的执行时机,修改WatchEffectAPIFlush.vue子组件,代码如下所示:

......
<script>
  export default {
    setup() {
      ......
      watchEffect(() => {
        console.log(titleRef.value);
      },{
        flush: "post" // 修改执行时机,支持 pre, post, sync
      })
      return { titleRef }
    }
  }
</script>

这里的flush:"post"是将推迟副作用的初始运行,直到组件的首次渲染完成才执行。当然flush 选项还接受 sync,这将强制效果始终同步触发。然而,这是低效的,应该很少需要。

保存代码,运行在浏览器后。刷新页面,我们会发现结果打印了1次(打印出元素)。

注意:Vue3.2+ 以后watchPostEffectwatchEffect 带有 flush: 'post' 选项的别名。watchSyncEffectwatchEffect 带有 flush: 'sync' 选项的别名。


3.1 watch侦听


watch的API完全等同于组件watch选项的Property:

  • watch需要侦听特定的数据源,并在回调函数中执行副作用。
  • 默认情况下它是惰性的,只有当被侦听的源发生变化时才会执行回调。

与watchEffect的比较,watch允许我们:

  • 懒执行副作用(第一次不会直接执行)。
  • 更具体的说明当哪些状态发生变化时,触发侦听器的执行。
  • 访问侦听状态变化前后的值。


3.1.1 侦听单个数据源


watch侦听函数的数据源有两种类型:

  • 一个getter函数:但是该getter函数必须引用可响应式的对象(比如reactive或者ref)。
  • 直接写入一个可响应式的对象,reactive或者ref(比较常用的是ref)。

下面通过几个案例来学习watch函数的使用。

案例一:watch侦听的数据源为一个getter函数。

我们在08_watch使用文件夹下新建:WatchAPI.vue组件。WatchAPI.vue子组件,代码如下所示:

<template>
  <div>
    <h4 >{{info.name}}</h4>
    <button @click="changeData">修改数据</button>
  </div>
</template>
<script>
  import { reactive, watch } from 'vue';
  export default {
    setup() {
      const info = reactive({name: "coderwhy", age: 18});
      // 1.侦听watch时,传入一个getter函数(该函数引用可响应式的对象)
      watch(() => info.name, (newValue, oldValue) => {
        // 侦听info对象中name的改变  
        console.log("newValue:", newValue, "oldValue:", oldValue);
      })
      const changeData = () => {
        info.name = "kobe"; // 改变info对象中的name
      }
      return {changeData,info}
    }
  }
</script>

可以看到,我们调用了watch函数来侦听info对象name属性的变化。其中watch函数需要接收两个参数,第一次参数是一个getter函数,该函数必须引用可响应式的对象。第二参数是侦听的回调函数,该函数会接收到一个新的值和一个旧的值,并在该函数中打印出新旧值。最后我们在App.vue根组件中导入和使用WatchAPI组件(不再贴代码)。

保存代码,运行在浏览器的效果,如图10-20所示。刷新页面,点击修改数据按钮来修改info中的name后,我们可以看到watch已经侦听到info中name发生了改变,并打印出新旧值。

image.png

                                图10-20  watch侦听的数据源为getter函数

案例二:watch侦听的数据源为reactive对象。

修改WatchAPI.vue子组件,代码如下所示:

......
<script>
  export default {
    setup() {
      const info = reactive({name: "coderwhy", age: 18});
      // 1.侦听watch时,传入一个getter函数
      // watch(() => info.name, (newValue, oldValue) => {
      //   console.log("newValue:", newValue, "oldValue:", oldValue);
      // })
      // 2.传入一个可响应式对象: reactive对象
      watch(info, (newValue, oldValue) => {
        // reactive对象获取到的newValue和oldValue本身都是reactive对象  
        console.log("newValue:", newValue, "oldValue:", oldValue);
      })
      const changeData = () => info.name = "kobe";
      return {changeData,info}
    }
  }
</script>

保存代码,运行在浏览器后刷新页面,点击修改数据按钮后,我们可以看到watch已经侦听到info中name发生了改变,并打印出新旧值(都为reactive对象)。

如果希望newValue和oldValue是一个普通的对象的话,我们可以这样侦听,代码如下所示:

<script>
  export default {
    setup() {
      .......
      // 2.传入一个可响应式对象: reactive对象
      // 如果希望newValue和oldValue是一个普通的对象,watch第一参数改成getter函数
      watch(() => {
        return {...info}
      }, (newValue, oldValue) => {
        console.log("newValue:", newValue, "oldValue:", oldValue);
      })
      ......
    }
  }
</script>

保存代码,运行在浏览器后刷新页面,点击修改数据按钮后,我们可以看到watch已经侦听到info中name发生了改变,并打印出新旧值(都为普通对象)。

案例三:watch侦听的数据源为ref对象。

修改WatchAPI.vue子组件,代码如下所示:

......
<script>
  export default {
    setup() {
      .....
      const name = ref("codeywhy");
      // watch侦听ref对象,ref对象获取newValue和oldValue是value值的本身  
      watch(name, (newValue, oldValue) => {
        console.log("newValue:", newValue, "oldValue:", oldValue);
      })
      const changeData = () => name.value = "kobe";
      return {changeData,info,name}
    }
  }
</script>

保存代码,运行在浏览器后刷新页面,点击修改数据按钮后,我们可以看到watch已经侦听到name发生了改变,并打印出新旧值(都是name的value)。


3.1.2 侦听多个数据源


侦听器还可以使用数组同时侦听多个源:

我们在08_watch使用文件夹下新建:WatchAPIMult.vue组件。WatchAPIMult.vue子组件,代码如下所示:

<template>
  <div>
    <h4 >{{info.name}} - {{name}}</h4>
    <button @click="changeData">修改数据</button>
  </div>
</template>
<script>
  import { ref, reactive, watch } from 'vue';
  export default {
    setup() {
      // 1.定义可响应式的对象
      const info = reactive({name: "coder", age: 18});
      const name = ref("why");
      const age = ref(20);
      // 2.侦听多数据源,参数一是一个数组:数组中可以有getter函数,ref对象,reactive对象
      watch([() => ({...info}), name, age],
           ([newInfo, newName, newAge], [oldInfo, oldName, oldAge]) => {
        console.log(newInfo, newName, newAge);
        console.log(oldInfo, oldName, oldAge);
      })
      const changeData = () => {
        info.name = "kobe";
        name.value = "jack"
      }
      return {changeData,info,name}
    }
  }
</script>

可以看到,我们调用了watch函数来侦听多个数据源。watch函数的第一个参数接收的是一个数组,该数组中是支持侦听getter函数,ref对象和reactive对象的数据源。接着我们给watch的第二个参数传入回调函数,该回调函数接收的新值和旧值都是数组类型,然后我们在该函数中分别打印了新值和旧值。最后我们在App.vue根组件中导入和使用WatchAPIMult组件(不再贴代码)。

保存代码,运行在浏览器的效果,如图10-21所示。刷新页面,点击修改数据按钮后,我们可以看到watch已经侦听到info中name和name都发生了改变,并打印出新旧值。

image.png

                                     图10-21  watch侦听多数据源


3.1.3 侦听响应式对象


如果我们希望侦听一个数组或者对象,那么可以使用一个getter函数,并且对可响应对象进行解构。

侦听响应式对象在上面的案例二中已经介绍过,下面看看侦听响应式数组,代码如下所示:

const names = reactive(["abc", "cba", "nba"]);
// 侦听响应式数组( 和对象的使用一样 )
watch(() => [...names], (newValue, oldValue) => {
  console.log(newValue, oldValue);
})
const changeName = () => {
  names.push("why");
}

如果是侦听对象时,我们希望侦听是一个深层的侦听,那么依然需要设置 deep 为true:

  • 也可以传入 immediate 立即执行。

我们在08_watch使用文件夹下新建:WatchAPIDeep.vue组件。WatchAPIDeep.vue子组件,代码如下所示:

<template>
  <div>
    <h4 >{{info.name}}</h4>
    <button @click="changeData">修改数据</button>
  </div>
</template>
<script>
  import { ref, reactive, watch } from 'vue';
  export default {
    setup() {
      // 1.定义可响应式的对象
      const info = reactive({
        name: "coderwhy", 
        age: 18,
        friend: {
          name: "kobe"
        }
      });
      // 2.侦听响应式对象
      watch(() => ({...info}), (newInfo, oldInfo) => {
        console.log(newInfo, oldInfo);
      }, {
        deep: true,
        immediate: true
      })
      const changeData = () => info.friend.name = "james"
      return {changeData,info}
    }
  }
</script>

可以看到,我们调用了watch函数来侦听一个对象。watch函数的第一个参数是一个getter函数,第二个参数传入回调函数,在该回调函数打印接收的新值和旧值,第三个参数一个watch的配置项。其中deep为true代表是一个深层的侦听,即当用户修改了info中friend对象的name也会被watch侦听到,如果为false则侦听不到。还有immediate为true代表watch的回调函数会先立即执行一次,当侦听到有数据变化时才再次执行该回调函数。最后我们在App.vue根组件中导入和使用WatchAPIDeep组件(不再贴代码)。

保存代码,运行在浏览器后。刷新页面,默认会先立即执行一次watch的回调函数,当点击修改数据按钮后,我们可以看到watch可以深层侦听info中firend对象的name发生了改变。

相关文章
|
18天前
|
存储 JavaScript 前端开发
vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
【10月更文挑战第21天】 vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
|
6天前
|
存储 前端开发 搜索推荐
淘宝 1688 API 接口助力构建高效淘宝代购集运系统
在全球化商业背景下,淘宝代购集运业务蓬勃发展,满足了海外消费者对中国商品的需求。掌握淘宝1688 API接口是构建成功代购系统的關鍵。本文详细介绍如何利用API接口进行系统架构设计、商品数据同步、订单处理与物流集成,以及用户管理和客户服务,帮助你打造一个高效便捷的代购集运系统,实现商业价值与用户满意度的双赢。
|
15天前
|
JavaScript 前端开发 开发者
Vue 3中的Proxy
【10月更文挑战第23天】Vue 3中的`Proxy`为响应式系统带来了更强大、更灵活的功能,解决了Vue 2中响应式系统的一些局限性,同时在性能方面也有一定的提升,为开发者提供了更好的开发体验和性能保障。
35 7
|
16天前
|
前端开发 数据库
芋道框架审批流如何实现(Cloud+Vue3)
芋道框架审批流如何实现(Cloud+Vue3)
38 3
|
15天前
|
JavaScript 数据管理 Java
在 Vue 3 中使用 Proxy 实现数据双向绑定的性能如何?
【10月更文挑战第23天】Vue 3中使用Proxy实现数据双向绑定在多个方面都带来了性能的提升,从更高效的响应式追踪、更好的初始化性能、对数组操作的优化到更优的内存管理等,使得Vue 3在处理复杂的应用场景和大量数据时能够更加高效和稳定地运行。
36 1
|
15天前
|
JavaScript 开发者
在 Vue 3 中使用 Proxy 实现数据的双向绑定
【10月更文挑战第23天】Vue 3利用 `Proxy` 实现了数据的双向绑定,无论是使用内置的指令如 `v-model`,还是通过自定义事件或自定义指令,都能够方便地实现数据与视图之间的双向交互,满足不同场景下的开发需求。
37 1
|
17天前
|
JavaScript 前端开发 API
Vue 3新特性详解:Composition API的威力
【10月更文挑战第25天】Vue 3 引入的 Composition API 是一组用于组织和复用组件逻辑的新 API。相比 Options API,它提供了更灵活的结构,便于逻辑复用和代码组织,特别适合复杂组件。本文将探讨 Composition API 的优势,并通过示例代码展示其基本用法,帮助开发者更好地理解和应用这一强大工具。
22 1
|
18天前
|
前端开发 JavaScript
简记 Vue3(一)—— setup、ref、reactive、toRefs、toRef
简记 Vue3(一)—— setup、ref、reactive、toRefs、toRef
|
18天前
Vue3 项目的 setup 函数
【10月更文挑战第23天】setup` 函数是 Vue3 中非常重要的一个概念,掌握它的使用方法对于开发高效、灵活的 Vue3 组件至关重要。通过不断的实践和探索,你将能够更好地利用 `setup` 函数来构建优秀的 Vue3 项目。
|
20天前
|
监控 安全 测试技术
我们为什么要API管理系统呢?
API 管理系统通过接口标准化与复用、简化开发流程、版本管理、监控与预警、访问控制、数据加密、安全审计、集中管理与共享、协作开发、快速对接外部系统和数据驱动的决策等多方面优势,显著提高开发效率、增强系统可维护性、提升系统安全性、促进团队协作与沟通,并支持业务创新与扩展。