vue3 就业课程 组件的变化
一、路由的变化
1、 vue3
三步打通路由页面切换
1. 安装
2. 创建并配置路由
3. 在启动文件里应用路由
1. 安装
npm i vue-router@next 袁老师安装的是测试版 ^4.0.0-beta.13
npm i vue-router 我默认安装的版本
"dependencies": {
"nprogress": "^0.2.0",
"vue": "^3.0.4",
"vue-router": "^4.5.0"
},2. 创建并配置路由
为了更好的 Tree-shaking 优化,vue3 里面变成了具名导出
没有构造函数 vue,导出的是具名函数 { createApp }
路由也一样,没有了构造函数,导出具名函数 { createRouter } 用该函数创建路由
/src/router/index.js
import {createRouter, createWebHistory} from "vue-router";
import routes from "./routes";
export default createRouter({
history: createWebHistory("/blog"), // 之前是mode: "history"
routes,
});通过导出的 {createWebHistory} 函数创建 history 对象,该对象记录页面的路由,
baseUrl 变成了 createWebHistory 的参数
手册
https://github.com/vitejs/vue-router-next
点击 Migration Guide 迁移文档(如何升级)
查看 Breaking Changes 不兼容的更改
路由配置
/src/router/routes.js
import Home from "../views/Home.vue";
import About from "../views/About.vue";
export default [
{path: "/", component: Home},
{path: "/about", component: About},
];3. 在启动文件里面应用路由插件(这里不是构造函数 Vue 应用插件了)
main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'; // 导入路由
createApp(App).use(router).mount('#app'); // 应用插件根组件里面使用导航 <router-link>,
以及路由匹配到的组件 <RouterView/>
App.vue
<template>
<div id="nav">
<router-link to="/">Home</router-link>
|
<RouterLink to="/about">About</RouterLink>
</div>
<RouterView/>
</template>
<script>
export default {
name: 'App',
}
</script>
<style>
body{
font-family: Aveir, Helvetica, Arial, sans-serif;
text-align: center;
color: #2c3e50;
}
#nav a{
font-weight: bold;
color: #2c3e50;
text-decoration: none;
}
#nav a.router-link-exact-active{
color: #42b983;
}
</style>2、vue2
路由版本
"dependencies": {
"core-js": "^3.8.3",
"vue": "^2.6.14",
"vue-router": "^3.5.1"
},以前导入构造函数 VueRouter
src/router/index.js
import Vue from 'vue';
import VueRouter from 'vue-router'; // 导入构造函数
import Home from "@/views/Home.vue";
import About from "../views/About.vue";
Vue.use(VueRouter)
const routes = [
{path: "/", component: Home},
{path: "/about", component: About},
]
const router = new VueRouter({
mode: "history",
baseURL: "/blog",
routes
})
export default router应用路由插件
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')二、异步组件
本节专门做 Home 组件
home 页面有多个区域,相当于很多组件共同组成的页面,一些组件是直接加载的,还有一些组件是异步加载的
有两个组件的功能太多了,
如果全面打包到一个 js 里面 Home 页面的 js 文件会很大,所以需要异步去加载这两个组件的 js,
也就是说这两个组件的区是域懒加载
1、基本使用
vue2 要配置一个动态的 import
vue3 配置的步骤
1. 要导入 { defineAsyncComponent } 函数来定义一样异步组件
2. 参数要传一个函数 () => {},该参数函数要返回一个 promise
3. 比如返回一个动态 import() 导入组件 Block3.vue,动态的 import 就是一个 promise
4. defineAsyncComponent 函数得到(返回)一个 Block3,然后注册这个 Block3
src/views/Home.vue
<template>
<div class="container">
<div class="block">
<h2>区域1</h2>
<p>
<button>打开蒙层</button>
</p>
</div>
<div class="block mid">
<h2>区域2</h2>
</div>
<div class="block big"><Block3/></div>
<div class="block big"><h2>区域4</h2></div>
<div class="block mid"><h2>区域5</h2></div>
<div class="block"><h2>区域6</h2></div>
</div>
</template>
<script>
import { defineAsyncComponent } from "vue";
import {delay} from "../util";
const Block3 = defineAsyncComponent(async () => {
await delay(3000);
return import("../components/Block3.vue");
});
export default {
components:{
Block3
},
}
</script>
<style scoped>
.container{
display: flex;
width: 1000px;
margin: 0 auto;
flex-wrap: wrap;
padding: 0 50px;
justify-content: space-between;
}
.block{
width: 200px;
margin: 15px;
height: 250px;
border: 1px solid #ebebeb;
border-radius: 3px;
box-shadow: 0 0 8px 0 rgba(232, 237, 250, 0.6), 0 2px 4px 0 rgba(232, 237, 250, 0.5);
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.mid{
width: 300px;
}
.big{
width: 400px;
}
</style>const Block3 = defineAsyncComponent( () => import("../components/Block3.vue") )
这个 block3 不是项目中的 Block3.vue 文件,它是由函数 defineAsyncComponent 返回的,
这个 <block3/> 组件要等待加载完成后才会显示出来
import("../components/Block3.vue")
这里 import 动态导入返回是一个 promies,resolve() 的时候是一个组件就可以了,也可以自己写一个 Promise()
const Block3 = defineAsyncComponent(() => {
return new Promise();
});平时开发的时候就直接返回 import 动态导入一个组件就可以了,但是为了看到效果让它慢一加载
1. defineAsyncComponent 的参数传一个 async 函数(它返回的一定是 promise)
2. delay 暂停一段时间在返回 import,这样达到测试的效果
const Block3 = defineAsyncComponent(async () => {
await delay(3000);
return import("../components/Block3.vue");
});两个需要异步加载的异步组件
<!-- src/components/Block3.vue --> <template> <h2>异步区域3</h2> </template> <!-- src/components/Block5.vue --> <template> <h2>异步区域5</h2> </template>
2、晋级配置
有这样的场景
1. 异步组件正在加载中,如何显示 loading 效果
2. 如果加载出错,如何显示
defineAsyncComponent 的参数除了配置一个 () =>{} 函数,还可以配置一个 {} 对象
其实配置对象是 vue2 的写法
const AsyncComponent = () => ({
// 需要加载的组件(应该是一个Promise对象)
component: @import("./MyComponent.vue"),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延迟时间,默认值是200毫秒
delay: 200,
// 如果提供了超时间且组件加载也超时了,则使用加载失败时的组件,默认值是Infinity
timeout: 3000
})vue3 的写法
defineAsyncComponent 配置一个对象
loader 属性的值为刚才作为参数的函数
loadingComponent 正在加载中的时候
errorComponent 当出错误是 reject 状态,这时候显示的组件
<script>
import { defineAsyncComponent, h } from "vue";
import {delay} from "../util";
import Loading from "../components/loading.vue";
import Error from "../components/Error.vue";
const Block3 = defineAsyncComponent({
loader: async () => {
await delay(3000);
if(Math.random() < 0.5){ // 加一个错误的场景,报错相当于reject
throw new TypeError();
}
return import("../components/Block3.vue");
},
loadingComponent: Loading, // 当promise在pending的时候,将显示这里的组件
errorComponent: {
render(){
return h(Error, "出错了");
}
}
});
export default {
components:{
Block3
}
}
</script>Error 组件对象导入进来了,如何渲染一个有属性的组件呢,就是如何传递该组件的 slot 内容呢?
组件就是一个对象,errorComponent: {} 属性这里可以配置一个新组件对象,
在新组件对象里面使用 rander 函数
rander 渲染一个 Error 组件,组件的内容是“出错了”
在 vue3 里面渲染的 h 函数变成了一个普通的函数,变成一个全局导出的普通 { h } 函数,
这样在任何地方甚至不在组件里面都可以渲染虚拟节点,虚拟节点就是一个普通对象
errorComponent: {
render( h ){
return h(Error, "出错了");
},
}
Loading 组件
src/components/Loading.vue
<template>
<svg viewBox="25 25 50 50" class="circular">
<circle cx="50" cy="50" r="20" fill="none" class="path"></circle>
</svg>
</template>
<style scoped>
.circular {
height: 42px;
width: 42px;
animation: loading-rotate 2s linear infinite;
}
.path {
animation: loading-dash 1.5s ease-in-out infinite;
stroke-dasharray: 90, 150;
stroke-dashoffset: 0;
stroke-width: 2;
stroke-width: 1;
stroke: #409eff;
stroke: #ff88cc;
stroke-linecap: round;
}
@keyframes loading-rotate {
100% {
transform: rotate(1turn);
}
}
@keyframes loading-dash {
0% {
stroke-dasharray: 1, 200;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -40px;
}
100% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -120px;
}
}
</style>错误文本用 slot 来替代
src/components/Error.vue
<template>
<h3><slot></slot></h3>
</template>
<style scoped>
h3 {
color: #f40;
}
</style>3、封装一个辅助函数
把定义异步组件的代码,封装一个辅助函数
src/utIl.js
import { defineAsyncComponent, h } from "vue";
import Loading from "../components/loading.vue";
import Error from "../components/Error.vue";
export function delay(duration){
if(duration){
duration = getRandom(1000, 5000);
}
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, duration);
});
}
export function getRandom(min, max){
return Math.floor(Math.random() * (max - min)) + min;
}
// 函数返回的是一个异步组件,得到一个异步组件,参数path是组件加载的路径
export function getAsyncComponent(path){
return defineAsyncComponent({
loader: async () => {
await delay(3000);
if(Math.random() < 0.5){
throw new TypeError();
}
return import(path);
},
loadingComponent: Loading,
errorComponent: {
render(){
return h(Error, "出错了");
},
},
});
}使用 getAsyncComponent 函数得到异步组件
然后注册,在模板中使用
scr/views/Home.vue
<template>
<div class="container">
<div class="block">
<h2>区域1</h2>
<p>
<button>打开蒙层</button>
</p>
</div>
<div class="block mid">
<h2>区域2</h2>
</div>
<div class="block big"><Block3/></div>
<div class="block big"><h2>区域4</h2></div>
<div class="block mid"><Block5/></div>
<div class="block"><h2>区域6</h2></div>
</div>
</template>
<script>
import {getAsyncComponent} from "../util/index.js";
const Block3 = getAsyncComponent("../components/Block3.vue");
const Block5 = getAsyncComponent("../components/Block5.vue");
export default {
components:{
Block3,
Block5
},
}
</script>三、异步页面
Home 页面也需要异步加载,页面本质上也是组件
道理是一样的,
使用 defineAsyncComponent 封装一个定义异步组件的方法
src/util/index.js
import { defineAsyncComponent, h } from "vue";
import Loading from "../components/loading.vue";
import Error from "../components/Error.vue";
import "nprogress/nprogress.css";
import NProgress from "nprogress"
NProgress.configure({
trickleSpeed: 50,
showSpinner: false,
color: "red"
})
export function delay(duration){
if(duration){
duration = getRandom(1000, 5000);
}
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, duration);
});
}
export function getRandom(min, max){
return Math.floor(Math.random() * (max - min)) + min;
}
// 得到一个异步页面
export function getAsyncPage(path){
return defineAsyncComponent({
loader: async () => {
NProgress.start();
await delay(3000);
const comp = await import(path);
NProgress.done();
return comp;
},
loadingComponent: Loading,
});
}
export function getAsyncComponent(path){
return defineAsyncComponent({
loader: async () => {
await delay(3000);
if(Math.random() < 0.5){
throw new TypeError();
}
return import(path);
},
loadingComponent: Loading,
errorComponent: {
render(){
return h(Error, "出错了");
},
},
});
}路由配置这里调用异步组件的方法
src/router/routes.js
import {getAsyncPage} from "../util/index.js";
// 导入异步组件
const Home = getAsyncPage("../views/Home.vue");
const About = getAsyncPage("../views/About.vue");
export default [
{path: "/", component: Home}, // 这里的就是异步组件了
{path: "/about", component: About},
];四、进度条
https://github.com/rstacruz/nprogress
npm i nprogress
测试进度条
1. 导入 nprogress.css
2. 导入 NProgress 对象
main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'; // 导入路由
import "nprogress/nprogress.css";
import NProgress from "nprogress"
NProgress.configure({
trickleSpeed: 50, // 进度条增长速度
showSpinner: false, // 去掉小圈圈
})
window.NProgress = NProgress;
// NProgress.start();
// NProgress.done();
// 应用插件
createApp(App).use(router).mount('#app');五、Teleport
蒙层的位置,组件的逻辑结构非常合理,但是真实的 dom 元素结构是不合理的,
组件结果表示逻辑解构,逻辑解构非常的合理,
但是真实的 dom 结构非常的不合理,蒙层的 div 最好出现 body 里面的最后是最合理的
Teleport 会把里面生成的真实 dom,放到 css 选择器选中的元素里面
<Teleport to="css选择器"> </Teleport>
Home.vue
<template>
<div class="container">
<div class="block">
<h2>区域1</h2>
<p>
<button @click="visibleModal = true">打开蒙层</button>
</p>{{isShowModal}}
<Teleport to="body">
<Modal v-if="visibleModal">
<button @click="visibleModal = false">关闭蒙层</button>
</Modal>
</Teleport>
</div>
<div class="block mid">
<h2>区域2</h2>
</div>
<div class="block big"><Block3/></div>
<div class="block big"><h2>区域4</h2></div>
<div class="block mid"><Block5/></div>
<div class="block"><h2>区域6</h2></div>
</div>
</template>
<script>
import {ref} from "vue";
import Modal from "../components/Modal.vue";
import {getAsyncComponent} from "../util/index.js";
const Block3 = getAsyncComponent("../components/Block3.vue");
const Block5 = getAsyncComponent("../components/Block5.vue");
export default {
components:{
Block3,
Block5,
Modal
},
setup(){
const visibleModal = ref(false);
return {
visibleModal,
}
}
}
</script>
<style scoped></style>蒙层组件
src/components/Modal.vue
<template>
<div class="modal">
<slot></slot>
</div>
</template>
<script>
</script>
<style scoped>
.modal{
display: flex;
justify-content: center;
align-items: center;
position: fixed;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.3);
left: 0;
top: 0;
}
</style>