1.png ⏳前言


这是我参与更文挑战的第26天,活动详情查看: 更文挑战

大家都知道,在我们平常的前端开发中,实现模态框和发起异步请求是再常见不过的事情了。但是呢,不管是用 vue2 和原生js的实现方式,从逻辑上来说都还不够独立,因此, vue3 推出了新的方法来解决此问题。

下面就带领大家一起来了解vue3新推出的 teleport 究竟有多神奇?以及如何用 suspense 发起多个异步的请求?

一起来学习吧~📚


一、👋用teleport实现打开模态框操作



1、teleport是什么


teleport ,字面意思就是 远距离传送 ,我们可以把它理解为传送门的意思。

大家都知道,传送门的意思就是从一个地方传送到了另外一个地方。而 vue3 为什么要用 teleport 来表达呢?

其实,有一个非常常见的需求就是,我们经常要通过点击一个按钮,来实现模态框的效果。而在 vue3 之前,我们基本上控制它都是点击后 上下会形成一个父子组件 的关系,这样子感觉独立性就没有那么强了。

2.png

因此, vue3 为了解决该问题,就用了 teleport 来解决。 teleport 就仿佛一个传送门,像上图这样,比如我们点击了 打开按钮 ,那么点击完了之后,使用 传送门 瞬间移动到另外一个地方(模态框 Model )。再点击 关闭按钮 传送门 模态框 Modal 就消失了。

通过这样的解释,相信大家对 teleport 有了一个基础的认识。


2、实现模态框功能


接下来我们就来用这个功能,实现一个模态框,控制组件的显示和隐藏。


(1)设置锚点


我们现在 vue3 项目下的 /public/index.html 设置一个 锚点 ,来放置组件的内容。具体代码如下:

<body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    //先行定义一个锚点
    <div id="modal"></div>
    <!-- built files will be auto injected -->
  </body>
复制代码


(2)定义子组件


接下来我们在 /src/components 下定义一个 子组件 ,命名为 Modal.vue 具体代码如下:

<template>
  <teleport to="#modal">
      <div id="center" v-if="isOpen">
          <h2><slot>this is a modal</slot></h2>
          <button class="btn2" @click="buttonClick">Close</button>
    </teleport>
</template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
    //父组件的数据需要通过props把数据传给子组件,props的取值可以是数组也可以是对象
    props: {
        isOpen: Boolean,
    //子组件向父组件传递数据
    //使用emits,更明确的显示组件的自定义事件有哪些
    emits: {
        'close-modal': null
    //props对应props的内容
    //context名字可以自定义,只要上下对应即可,用来触发emit的内容
    setup(props, context){
        const buttonClick = () => {
            context.emit('close-modal')
        return{
            buttonClick
</script>
<style>
#center{
    width:200px;
    height:200px;
    border:2px solid rgb(105, 165, 56);
    text-align: center;
    border-radius: 2px;
    margin: 50px auto 0;
.btn2{
  background: #1971c9;
  border:none;
  padding: 8px;
  border-radius: 5px;
  color: #fff;
  cursor: pointer;
</style>
复制代码


(3)定义父组件


之后我们再来定义一个父组件,命名为 index.vue 具体代码如下:

<template>
    <button class="btn1" @click="openModal">打开模态框</button>
    <modal :isOpen="modalIsOpen" @close-modal="onModalClose">
      My Modal!!!
    </modal>
</template>
<script lang="ts">
import { ref, defineComponent} from 'vue'
import Modal from './components/Modal.vue'
export default defineComponent({
  name: 'App',
  components: {
    Modal
  setup(){
    //添加响应式对象控制是否显示
    const modalIsOpen = ref(false)
    //打开模态框事件
    const openModal = () => {
      modalIsOpen.value = true
    //关闭模态框事件
    const onModalClose = () => {
      modalIsOpen.value = false
    return{
      modalIsOpen,
      openModal,
      onModalClose
</script>
<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2d4c6b;
  margin-top: 60px;
.btn1{
  background: #1971c9;
  border:none;
  padding: 16px;
  border-radius: 5px;
  color: #fff;
  cursor: pointer;
</style>
复制代码

现在我们来看下浏览器的显示效果:

1.png

大家可以看到,通过 teleport 的方式,现在的模态框成功显示在 id app div 同一层下,达到了 相互独立 ,而不再是父子层级的结果。

在上面的案例中,我们学习到了通过使用 vue3 新推出的 teleport 特性,将组件渲染到另外一个 DOM 节点的方法,这样使得组件之间的独立性更强。


二、🤚用Suspense发起一个异步请求



1、Suspense是什么


我们都知道,在 web 世界中,经常遇到很多的异步请求困境。在发起异步请求时,我们往往需要去判断这些异步请求的状态,然后呢,根据这些请求来展示不同的界面。

那现在呢, vue3 推出了一个新的内置组件 Suspense Suspense 是一个特殊的组件,它会有两个 template slot ,刚开始会渲染 feedback 内容,直到 达到某个条件以后 ,才会渲染正式的内容,也就是default的内容。这样呢,进行异步内容的渲染就会变得特别简单。

同时值得注意的是,如果使用 Suspense ,要返回一个 promise 而不是一个对象。


2、用Suspense发起一个异步请求


接下来我们使用 Suspense 发起一个异步请求

首先我们在项目下定义一个 子组件 ,命名为 AsyncShow.vue 具体代码如下:

<template>
  <h1>{{result}}</h1>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
  setup() {
    //使用Suspense需要返回一个对象
    return new Promise((resolve) => {
      setTimeout(() => {
        return resolve({
          result: '10000'
      }, 3000)
</script>
复制代码

之后在项目下再定义一个 父组件 ,命名为 DogShow.vue 具体代码如下:

<template>
  <div id="app">
    <Suspense>
      <template #default>
        <async-show />
      </template>
      <template #fallback>
        <h1>Loading !...</h1>
      </template>
    </Suspense>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import AsyncShow from './components/AsyncShow.vue'
export default defineComponent({
  name: 'App',
  components: {
    AsyncShow,
  setup() {
</script>
<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
  font-size: 6rem;
</style>
复制代码

最终浏览器的显示效果如下:

2.png

大家可以看到,通过 Suspense ,可以很轻易的 发起一个异步请求 。刚开始是 fallback状态 ,之后达到 3s 的时间之后,切换到 default的状态 ,显示出对应的异步请求内容。


3、用Suspense发起多个异步请求


我们不满足于现状,且互联网千奇百怪的,我们总不能一直只发送一个异步请求吧!所以,接下来我们就来实现发起多个异步请求的效果。

首先我们用一个免费的 在线API ,来发起一个请求。接下来我们在项目的 components 文件下,再定义一个子组件,命名为 DogShow.vue 具体代码如下:

<template>
  <img :src="result && result.message">
</template>
<script lang="ts">
import axios from 'axios'
import { defineComponent } from 'vue'
export default defineComponent({
  async setup() {
    const rawData = await axios.get('https://dog.ceo/api/breeds/image/random')
    return {
      result: rawData.data
</script>
复制代码

接下来我们再把以上子组件的内容添加到父组件中, 具体代码如下:

<template>
  <div id="app">
    <Suspense>
      <template #default>
        <async-show />
        <dog-show />
      <template #fallback>
        <h1>Loading !...</h1>
      </template>
    </Suspense>
</template>
<script lang="ts">
import AsyncShow from './components/AsyncShow.vue'
import DogShow from './components/DogShow.vue'
export default {
  name: 'App',
  components: {
    AsyncShow,
    DogShow
  setup() {
</script>
<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
  font-size: 6rem;
</style>
复制代码

最终浏览器的显示效果如下:

1.png

大家可以看到,我们同时发起了两个异步请求,并且在Suspense中的default插槽里面同时使用。同样的,浏览器会先显示fallback的内容,之后等到时机到了,就显示我们请求的内容。

依据这样的例子,显示更多的请求也同样有效。这样对比起来,发送多个异步请求是不是就方便了许多。


4、如何抓取错误


学完上面的内容,相信大家对Suspense的用法已经有所了解。那么,网络请求千奇百怪的,总不能每次都能够很顺畅的发起请求对吧。所以呢,我们来需要再来学习一下,当网络请求失败时, 如何抓取Supsense包裹下的错误

这个时候我们可以使用一个钩子函数,这个函数叫做 onErrorCaptured ,接下来我们来看下怎么抓取。

我们将父组件 index.vue 进行改造,具体代码如下:

<template>
  <div id="app">
    <p>{{error}}</p>
    <Suspense>
      <template #default>
        <async-show />
        <dog-show />
      <template #fallback>
        <h1>Loading !...</h1>
      </template>
    </Suspense>
</template>
<script lang="ts">
import { onErrorCaptured } from 'vue'
import AsyncShow from './components/AsyncShow.vue'
import DogShow from './components/DogShow.vue'
export default {
  name: 'App',
  components: {
    AsyncShow,
    DogShow
  setup() {
    const error = ref(null)
    onErrorCaptured((e: any) => {
      error.value = e
      return true
    return{
        error
</script>
<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
  font-size: 6rem;
</style>
复制代码

此时我们再将 DogShow.vue 的接口进行修改,让其变成一个无效的API。 代码如下:

const rawData = await axios.get('https://dog.ceo/api/breeds/image')
复制代码

接下来我们就来看一下 浏览器的运行效果:

3.png

大家可以看到,修改成无效的 API 后,狗狗的图片也不显示了,最后最上方就是通过 onErrorCaptured 这个生命周期捕捉到的错误,清晰明了。


三、🖐️结束语



到这里, teleport Suspense 的内容就讲解结束啦!相信大家对传送门的神奇之处也有了一定的了解,对 Suspense 的独到之处也感受了一番。

vue3持续学习,更新永不停歇……



尤雨溪:Vue3 考虑彻底放弃 IE 浏览器
凌晨时分,尤雨溪突然在知乎上发布了一个消息,宣布了一个提案:Vue3 将不再支持 IE11,来通过详细的 RFC 了解一下为什么 Vue 团队打算做出这个决策。
学习Vue3 第十九章(Teleport传送组件)
Teleport 是一种能够将我们的模板渲染至指定DOM节点,不受父级style、v-show等属性影响,但data、prop数据依旧能够共用的技术;类似于 React 的 Portal。