zl程序教程

您现在的位置是:首页 >  前端

当前栏目

Vue3新特性体验--下(各式各样API,完结篇)

Vue3API -- 体验 特性 完结篇
2023-09-11 14:17:48 时间

这是Vue3新特性体验的最后一篇,推荐先把上、中篇看完,然后继续本文的阅读。

Vue3新特性体验--上(内附简单实例,可直接使用)_前端菜小白leo的博客-CSDN博客

Vue3新特性体验--中(Composition API)_前端菜小白leo的博客-CSDN博客

 依然使用与上、中篇同样的简单实例。回顾一下实例目录与页面展示

接下来,继续Vue3新特性的体验之旅。

provideinject

我们已经了解父子组件通信可以使用props跟emit,那么跨层级(祖孙)通信该如何?可以使用provideinject可以实现跨层级组件通信。

添加一个Child.vue,其为B.vue的子组件,而B.vue为HelloWorld.vue的子组件,即Child.vue为HelloWorld.vue的孙组件。组件关系代码如下(只展示模板片段)

//HelloWorld.vue
<template>
  <div class="hello">
    <h1>我是vue3项目</h1>
    <div class="content">
      <A/>
      <B/>
    </div>
    <div>
      <p>我是HelloWorld组件</p>
    </div>
  </div>
</template>
//B.vue
<template>
  <div class="b-style">
    <p>我是B组件</p>
    <Child />
  </div>
</template>
//Child.vue
<template>
  <div class="child-style">
    <p>我是Child组件</p>
  </div>
</template>

组件关系展示页面(只关注B组件部分)

组件搭建好,开始演示使用provideinject

HelloWorld.vue

<template>
  <div class="hello">
    <h1>我是vue3项目</h1>
    <div class="content">
      <A/>
      <B/>
    </div>
    <div>
      <p>我是HelloWorld组件</p>
    </div>
  </div>
</template>

<script>
import { provide } from '@vue/runtime-core'
import A from './A.vue'
import B from './B.vue'
export default {
  components:{
    A,
    B
  },
  name: 'HelloWorld',
  setup () {
    provide('fromHW','HelloWorld的消息')   //使用provide向下传递
    return {
      
    }
  }
}
</script>

Child.vue

<template>
  <div class="child-style">
    <p>我是Child组件</p>
    <span>{{msg}}</span>
  </div>
</template>

<script>
import { inject } from '@vue/runtime-core'
export default {
  name: 'Child',
  setup(){
    let msg = inject('fromHW')   //通过inject拿到provide中传递的消息
    return {
      msg
    }
  }
}
</script>

页面效果,正常展示

注意:inject获取到对应的provide,则第一个字段值必须一致,如该例中的“fromHW”。

这时候Child已经拿到HelloWorld中传递下来的消息,那么反过来呢,Child向HelloWorld传递消息在Vue2中,一种常用方式是事件总线Bus实现跨组件通信

Vue组件间的通信方式(多种场景,通俗易懂,建议收藏)_前端菜小白leo的博客-CSDN博客

但是在Vue3中移除了$on、$off、$once,导致无法使用Bus,但是官方推荐的另外一种取代方案,原理跟Bus一样,那就是mitt.js。

mitt.js

安装mitt

npm i mitt -s

新建mitt.js

import mitt from 'mitt'
const emitter = mitt();

export default emitter; 

开始使用mitt.js

Child.vue

<template>
  <div class="child-style">
    <p>我是Child组件</p>
    <span>{{msg}}</span>
    <button @click="changeMsg">发送消息</button>
  </div>
</template>

<script>
import { inject } from 'vue';
import emitter from '../mitt'
export default {
  name: 'Child',
  setup(){
    let msg = inject('fromHW')
    let changeMsg = () => {   //执行该方法,触发mitt的emit
      emitter.emit('fromChild','Child组件的消息')
    }
    return {
      msg,
      changeMsg
    }
  }
}
</script>

HelloWorld.vue

<template>
  <div class="hello">
    <h1>我是vue3项目</h1>
    <div class="content">
      <A/>
      <B/>
    </div>
    <div>
      <p>我是HelloWorld组件</p>
      <span>{{msg}}</span>
    </div>
  </div>
</template>

<script>
import { ref,provide } from '@vue/runtime-core';
import emitter from '../mitt'
import A from './A.vue'
import B from './B.vue'
export default {
  components:{
    A,
    B
  },
  name: 'HelloWorld',
  setup () {
    let msg = ref('')
    provide('fromHW','HelloWorld的消息')   //向下传递
    emitter.on('fromChild',val => {   //监听mitt中的emit
      msg.value = val
    })
    return {
      msg
    }
  }
}
</script>

点击按钮,实现Child向HelloWorld发消息

注意:与provide和inject注意点一致,触发和监听的字段需一致,才能达到指定的通信效果,如该例中的“fromChild”。

既然跨层级组件之间能使用这两种方式,父子组件当然也可以使用。

Teleport

提供一种方法,可以让一个模板插入到该组价或者父组件外的html标签(很可能是body)里面,可以称它为穿梭门。

未使用teleport时:

<template>
  <div class="child-style">
    <p>我是Child组件</p>
    <span>{{msg}}</span>
    <button @click="changeMsg">发送消息</button>
    <div class="test">
      <span>Child未使用teleport</span>
    </div>
  </div>
</template>

看下页面元素结构

试着使用teleport,穿梭到body

<template>
  <div class="child-style">
    <p>我是Child组件</p>
    <span>{{msg}}</span>
    <button @click="changeMsg">发送消息</button>
    <!-- <div class="test">
      <span>Child未使用teleport</span>
    </div> -->
    <teleport to="body">
      <div class="test">
        <span>Child使用teleport</span>
      </div>
    </teleport>
  </div>
</template>

Suspense

官网对其解释

我们有时候想在加载异步组件时渲染一些后备内容(比如loading等),就可以使用suspense,让我们可以优化用户体验。

首先,对Child.vue进行修改,模拟异步加载组件

<template>
  <div class="child-style">
    <p>我是Child组件</p>
    <span>{{msg}}</span>
  </div>
</template>

<script>
export default {
  name: 'Child',
  setup(){
    return new Promise((resolve)=>{
      setTimeout(()=>{
        resolve({
          msg:'异步加载消息'
        })
      },2000)
    })
  }
}
</script>

然后修改父组件B.vue,使用Suspense,其有两个插槽,它们都只接收一个直接子节点,default插槽里的节点会尽可能展示出来。如果不能,则展示fallback插槽里的节点。

<template>
  <div class="b-style">
    <p>我是B组件</p>
    <suspense>
      <!-- v-slot:default可以简写成#default -->
      <template v-slot:default>
        <AsyncChild />
      </template>
      <!-- v-slot:fallback可以简写成#fallback -->
      <template v-slot:fallback>
        <div>
          <span style="color:#666666">正在加载,请稍后...</span>
        </div>
      </template>
    </suspense>
  </div>
</template>

<script>
import { defineAsyncComponent } from "vue";   //vue3中使用其引入异步组件
const AsyncChild = defineAsyncComponent(() => import("./Child.vue"));
export default {
  name: "B",
  components: {
    AsyncChild,
  },
  setup() {
    return {};
  },
};
</script>

B组件在加载时,因为Child为异步组件,因此这个时候suspense插槽default无法展示,此时先展示fallback里面的内容。

 模拟两秒后(setTimeout) ,Child组件加载完毕,则suspense显示插槽default中的内容。

重要的是,异步组件不需要作为suspense的直接子节点。它可以出现在组件树任意深度的位置,且不需要出现在和suspense自身相同的模板中。只有所有的后代组件都准备就绪,该内容才会被认为解析完毕。如下,当Child加载完毕,“显示完成”才一起出现。

<template>
  <div class="b-style">
    <p>我是B组件</p>
    <suspense>
      <!-- v-slot:default可以简写成#default -->
      <template v-slot:default>
        <div>
          <span>显示完成</span>
          <AsyncChild />
        </div>
      </template>
      <!-- v-slot:fallback可以简写成#fallback -->
      <template v-slot:fallback>
        <div>
          <span style="color:#666666">正在加载,请稍后...</span>
        </div>
      </template>
    </suspense>
  </div>
</template>

将 <suspense> 跟 <transition> 和 <keep-alive> 组件相结合是常见的情形。这些组件的嵌套顺序对于它们的正确工作很重要。更多详细内容请参考官方文档:

Suspense | Vue.jshttps://v3.cn.vuejs.org/guide/migration/suspense.htmlv-model  v-bind.sync

v-model | Vue.js

语法糖script setup

尽管现在setup使用起来比较方便,但是可以发现一些地方,组件都已经引入了还需要注册,而且当一个组件的数据和方法过多时,需要return很多行,代码量可能会很多。Vue3提供一个语法糖script setup,即在script加入setup,可以不用return,template也可以获取到。

将HelloWorld.vue改成script setup的形式

修改前:

<script>
import { ref,provide } from 'vue';
import emitter from '../mitt'
import A from './A.vue'
import B from './B.vue'
export default {
  components:{
    A,
    B
  },
  name: 'HelloWorld',
  setup () {
    let msg = ref('')
    provide('fromHW','HelloWorld的消息')   //向下传递
    emitter.on('fromChild',val => {   //监听事件
      msg.value = val
    })
    return {
      msg
    }
  }
}
</script>

修改后代码量少了许多: 

<template>
  <div id="hello">
    <h1>我是vue3项目</h1>
    <div class="content">
      <A/>
      <B/>
    </div>
    <div>
      <p>我是HelloWorld组件</p>
      <span>{{msg}}</span>
    </div>
  </div>
</template>

<script setup>
  import { ref,provide } from 'vue';
  import emitter from '../mitt'
  import A from './A.vue'
  import B from './B.vue'
  let msg = ref('')
  provide('fromHW','HelloWorld的消息')   //向下传递
  emitter.on('fromChild',val => {   //监听事件
    msg.value = val
  })
</script>

验证页面显示正常,无警告,无报错。

但是没有了setup函数,那么props,emit,attrs该如何获取呢,就要介绍一下新的语法了。setup script语法糖提供了三个新的API来供我们使用:definePropsdefineEmitsuseContext

defineProps:用来接收父组件传来的值props。
defineEmits:用来声明触发的事件表。
useContext:用来获取组件上下文context。

HelloWorld.vue

<template>
  <div id="hello">
    <h1>我是vue3项目</h1>
    <div class="content">
      <A/>
      <B msg="props传递的消息" @changeB="handleClick"/>
    </div>
    <div>
      <p>我是HelloWorld组件</p>
      <span>{{msg}}</span>
    </div>
  </div>
</template>

<script setup>
import { ref,provide } from 'vue';
  import emitter from '../mitt'
  import A from './A.vue'
  import B from './B.vue'
  let msg = ref('')
  provide('fromHW','HelloWorld的消息')   //向下传递
  emitter.on('fromChild',val => {   //监听事件
    msg.value = val
  })
  let handleClick = val => {  //emit回调
    alert(val)
  }
</script>

B.vue

<template>
  <div class="b-style">
    <p>我是B组件</p>
    <suspense>
      <!-- v-slot:default可以简写成#default -->
      <template v-slot:default>
        <div>
          <span>显示完成</span>
          <p>{{props.msg}}</p>
          <button @click="changeB">emit</button>
          <AsyncChild />
        </div>
      </template>
      <!-- v-slot:fallback可以简写成#fallback -->
      <template v-slot:fallback>
        <div>
          <span style="color:#666666">正在加载,请稍后...</span>
        </div>
      </template>
    </suspense>
  </div>
</template>

<script setup>
  import { defineAsyncComponent, defineProps, defineEmits} from "vue";
  let AsyncChild = defineAsyncComponent(() => import("./Child.vue"));
  let emit = defineEmits(['changeB'])
  let props = defineProps({    //接收父组件放在子组件的msg
    msg: String,
  })
  let changeB = () => {     //触发父组件放在子组件的事件changeB
    emit('changeB', 'emit传递的消息')
  }
</script>

刷新页面显示,defineProps使用成功。

点击按钮,alert弹窗,defineEmits使用成功。 

对比未使用script setup

props: ['msg'],
setup(props, {attrs, slots, emit}) {
  const changeB = () => {
    emit('changeB','emit传递的消息');
  };
  return {
    changeB,
  };
}

总结:通过简单实例体验Vue3一些常用的新特性,感受Vue3带来的便利性。

Vue3内容远远不止于此,更多Vue3详细内容请前往官方地址学习

介绍 | Vue.jsVue.js - The 渐进式 JavaScript 框架https://v3.cn.vuejs.org/guide/migration/introduction.html