Vue3
一、Vue的发展历程
- 2014年 Vue
- 2016年Vue2
- 2020年09月,Vue3发布
二、项目构建工具
- Vue CLI脚手架工具(基于webpack)
- Vite(推荐)
三、Vite构建Vue3项目
1.创建项目+下载依赖+启动项目
yarn create vite 项目名称 --template vue-ts//创建项目
yarn //下载依赖
yarn dev//启动项目
注意
: vite创建项目时可以通过附加的命令行选项直接指定项目名称和你想要使用的模板
yarn create vite 项目名称 --template #附加命令
//常用附加:
vue:vue3项目
vue-ts:vue+ts的项目
react:react项目
react-ts:react+ts的项目
2.VS Code插件安装
注意:将vue2 下载的插件Vetur需要先禁用
四、vue3语法(与vue2对比)
在template中使用Vue3的语法和Vue2几乎没有差别,但是在
1、template根节点
在Vue3中,template不再需要根节点
<template>
<div></div>
<div></div>
</template>
2、子组件渲染
在Vue3中,父组件中如果需要渲染子组件,只需要“引入”和“渲染”即可
<script setup lang="ts">中引入:import 组件 from “路径”
<template>中渲染:<组件/>
3、setup
Vue3从最早的测试版到正式版再到正式版后去的新提案,每一个setup语法都不太一样
推荐用新提案语法:快捷键v3 =>v3-ts-setup
<script setup lang="ts">
</script>
4、组件的数据
-
通过*ref()*来定义基本数据
//引入和定义: import { ref } from "vue" const num=ref( 0 )//变量num的初始值是0 //在js中使用: console.log(num.value) //在html中使用: {{num}}
-
通过*reactive()*来定义引用数据
//引入和定义 import { relative } from“vue” const state=relative({ num:2, name:"张三" }) //在js和html中使用方法一致 console.log(state.num) {{state.name}}
注意:通过**reactive()**定义的引用数据的地址不能被改变,否则数据的响应性会丢失
5、组件的事件
绑定还是在标签上@click
事件响应函数直接写在script中即可
<button @click="add">增加</button>
<script setup>
const add = () => {
num.value++;
}
</script>
6、组件的计算属性
从vue中引入computed
import { computed } from "vue"
const sum = computed(()=>{
......
return 计算结果;
})
//使用:
{{sum}}
7、侦听器
引入watch,调用watch(),其中包含两个箭头函数和一个对象作为参数;
参数一是将侦听的数据作为返回值返回
参数二是当被侦听的数据发生变化时执行的函数
参数三是一个对象:配置是否立即侦听和深度侦听
import { watch } from "vue"
watch(
()=>num.value,//参数一:侦听对象
(newValue,oldValue)=>{
console.log("数据num.value发生了变化")
},//参数二:侦听对象变化时执行的函数
{
immediate: true,
deep:true
}//参数三:配置项
)
8、watchEffect(新增的(自动)侦听器)
它接收一个函数作为参数
作用:当watchEffect()中用到的数据(依赖)变化时,都会重新执行其中的函数
import { watchEffect } from "vue"
watchEffect( ()=>{
console.log("你好",num.value)
})
//当num值发生变化时,watchEffect()将会重新执行
watchEffect和watch的区别
- 默认情况下,watchEffect会在页面首次加载时执行,而watch不会
- watch可以接收到数据变化前后的值,而watchEffect没有
- watch需要指定侦听的数据,而watchEffect自动侦听函数中依赖的数据
9、生命周期函数
Vue2 | Vue3 | 备注 |
---|---|---|
beforeCreate | / | |
created | / | |
beforeMount | onBeforeMount组件挂载前 | |
mounted | onMounted挂载后 | 此处获取节点、发送请求、绑定事件、设置定延时器等 |
beforeUpdate | onBeforeUpdate更新前 | |
updated | onUpdated更新后 | |
beforeDestroy | onBeforeUnmount销毁前 | |
destroyed | onUnmounted销毁后 |
使用前先引入
import { onMounted } from "vue"
onMounted(()=>{
console.log("组件挂载完成时执行的声明周期函数")
})
五、组件之间的传值
父子组件之间
1. 父传子(definedProps)
-
父组件传递:自定义属性
-
子组件接收:defineProps()
//父中传递: <Children :num="num"/> //子中接收: <script setup lang="ts"> defineProps({ num:Number }) </script> 在template中使用:{{props.num}} 在js中使用父传入的值: const props=defineProps({num:Number}) console.log(props.num)
2. 子传父(defineEmits)
- 子组件传递:注册自定义事件并触发事件
- 父组件接收:自定义事件的响应函数
//子中传递
<button @click="toFather">传父</button>
js中:
//1.注册父的自定义事件
const emit=defineEmits(["getData"])
//2.触发自定义事件传值
const toFather=()=>{
emit("getData自定义事件名称",data要传的值)
}
//父组件接收
<Children :num="num" :getData="getData" />
js中:
cosnt getData=(data)=>{
使用data即可
}
Vue3全家桶
一、创建Vue3项目
yarn create vite 项目名称 --template vue-ts
yarn
yarn dev
二、安装其他依赖
1、路由 Vue Router
yarn add vue-router
2、状态机 Pinia
Vue3的项目中,可以选择的状态机有两种
- Vuex
- Pinia
yarn add pinia
3、网络请求 axios
yarn add axios
4、UI框架 Element-plus / antd Vue
Vue3的项目比较主流的UI框架
- Element-plus
- Ant Design Vue
yarn add ant-design-vue
5、css预处理器 less
yarn add less less-loader -D
三、项目目录结构
在src下:
--src
--apis//请求
--assets//静态资源:图片、字体、图标...
--components//提取的公共组件
--router//路由配置
--store//状态机
--styles//公共样式
--types//公共 TS 类型约束
--utils//工具文件,例如axios的封装
--views//页面类组件(参与路由配置的组件)
路由配置
一、配置文件
1、创建文件夹
src
| --- router
| | --- index.ts
注意:创建空ts文件时报错,解决方法:在tsconfig.json文件中将第十行isolatedModules的值改为false即可
2、在index中配置路由
//1、引入
import { createRouter,createWebHashHistory,createWebHistory } from "vue-router"//引入vue-router插件
import LoginView from "组件路径"//引入页面组件
//2、路由信息配置(数组)
const routes=[
{
path:"/login",
name:"Login",
component:LoginView
},
{...},
...
]
//3、路由配置
const router=createRouter({
routes,//路由数组
//路由模式
//histort:createWebHashHistory() #hash模式
histort:createWebHistory() //history模式
})
//4、暴露路由
export default router
3、全局挂载路由
在main.ts中配置
import { createApp } from "vue"
import App from "./App.vue"
#1.引入路由
import router from “路由配置文件路径”
const app = createApp( App )//创建app对象
#2.全局挂载路由
app.use(router)
app.mount( "#app" )//挂载app组件
4、路由配置
const routes=[
{//路由重定向
path:"/",
redirect:"/home"
},
{
path:"/login",
name:"Login",
component:LoginView
},
{//一级路由
path:"/",
name:"Home",
component:HomeView,
children:[//二级路由
{...}
]
},
...
]
5、路由出口 router-view
在app文件中,将内容全删了,配置一级路由的路由出口
<template>
<router-view></...>
</template>
6、路由跳转(2种)
-
通过 router-link 标签跳转
<router-link to="/">首页</router-link>
-
useRouter跳转
import { useRouter } from "vue-router" const router = useRouter() router.push("/")//跳转 //补充: useRoute()可以获取到路由配置信息,相当于vue2的this.$route
二、动态路由
生成完整的子路由数组—通过全局前置守卫—动态生成符合权限的路由数组
注意:在vue3的路由守卫中,可以不用写next(),直接return 路由 即可
全局前置守卫中的逻辑:
- 判断用户访问的路由是否是项目中存在的子路由
- 存在路由,判断用户是否登录
- 登录后。判断用户访问路由是否在其权限下
动态生成路由( addRoute(路由对象) )
注意:添加路由之前务必判断是否已存在(通过getRoutes()可以获取到目前已经存在的所有的路由对象数组),如果存在了就不能再添加该路由了
-
动态添加404路由
router.addRoute({ path: " /:pathMatch( .* )* ", name: "NotFound", component: NotFound })
-
动态添加权限路由数组
完整代码
const routes=[
重定向和登录路由信息对象{path,name,component}
]
const indexChildRoutes=[
所有的子路由信息对象{...}
]
......
//在路由守卫中进行逻辑判断和动态添加
router.beforeEach( (to,from)=>{
//首先判断用户的路径是否在项目所有路径中
const userRoute= indexChildRoutes.find( route=> "/"+route.path==to.fullpath )
if(userRoute ){
//访问路径存在的前提下,判断用户是否已登录(用token或userInfo都可以)
const userInfo=localstorage.getItem("userInfo")
if(userInfo){
//已登录时,获取用户权限下的所有路径
const menus=JSON.parse(userInfo).roles.menus
const isAuthRoute=menus.includes(to.fullpath)
//如果在权限下,则直接添加到路由数组并跳转到该路由
if(isAuthRoute){
//判断用户路由是否已经有了,有就直接跳转,没有就添加
const allRoutes = router.getRoutes()
const authRoute = allRoutes.find(item=>item.path==to.fullpath)
if( authRoute ){
return to.fullpath
}else{
router.addRoutes({
path:"/",
name:"Home",
component:HomeView,
children:indexChildRoutes.filter( route=>menus.includes("/"+route.path) )
})
return to.fullpath
}
}else{
//不在权限中,则到404
const allRoutes = router.getRoutes()
const notFoundPath = allRoutes.find(route=>route.path== "/:pathMatch( .* )* ")
if(!notFoundPath){
router.addRoute({
path: " /:pathMatch( .* )* ",
name: "NotFound",
component: NotFound
})
}
return to.fullpath
}
}else{
//如果未登录,则提示并跳到登录页面
alert(”请先登录!“)
return "/login"
}
}else{
//用户访问的路径不存在,则动态添加404页面
const allRoutes = router.getRoutes()
const notFoundPath = allRoutes.find(route=>route.path== "/:pathMatch( .* )* ")
//判断:不存在用户访问路由的情况下
if(!notFoundPath){
router.addRoute({
path: " /:pathMatch( .* )* ",
name: "NotFound",
component: NotFound
})
}
return to.fullpath
}
} )
antd使用
1、下载和注册
-
注册:在main.ts中注册
import { createApp } from "vue" import App from "./App.vue" #1.引入antd import Antd from 'ant-design-vue' #2.引入其css样式文件 import 'ant-design-vue/dist/antd.css'; import router from “路由配置文件路径”//引入路由 const app = createApp( App )//创建app对象 app.use(router)//注册路由 #3.注册antd app.use(Antd) app.mount( "#app" )//挂载app组件
自定义指令
应用场景:按钮权限(不同角色可以分别使用增删改查等页面的按钮)
在vue3中所有v开头的都会被当成一个指令,其中有一个mounted方法,每当标签上的指令执行时,就会执行mounted函数。它接收参数el(表示绑定了该指令的节点)
- 全局(main.js中):通过directive()
//注册
app.directive("指令名称:delete",{
mounted(el){
const { role } = JSON.parse(localStorage.userInfo)
if(role.name=="普管"){
el.remove()
}
}
})
//使用
<a v-delete>删除</a>
- 局部(组件中)
//定义
const vDelete = {
mounted(el){
const { role } = JSON.parse(localStorage.userInfo)
if(role.name=="普管"){
el.remove()
}
}
}
//使用
<a v-delete>删除</a>
vue3插槽
一、默认插槽
子:slot标签
父:子组件标签内部写插槽内容
//Child:
<slot>默认插槽内容</slot>
//Father:
<Child>
插槽内容
</Child>
二、具名插槽
子:带name属性的slot标签
父:在子标签中通过v-slot : slotname
来指定插入内容和位置
//Child:
<slot name='header'></slot>
...
<slot name='footer'></slot>
//Father:
<Child>
<template v-slot : header>
插槽内容
</template>
<template v-slot : footer>
插槽内容
</template>
<Child>
三、动态插槽
子:同上
父:slotname通过内部状态定义
//Child:
<slot name='header'></slot>
...
<slot name='footer'></slot>
//Father:
<Child>
<template v-slot : header>
插槽内容
</template>
<template v-slot : [state.slotName]>
动态插槽内容
</template>
<Child>
const state=reactive({ slotname: 'footer' })
作用:可以将父组件的内容动态渲染到到子组件插槽接口中。
四、插槽传值
//Child:
<slot :title='state.headerTitle 'name='header'></slot>
...
<slot name='footer'></slot>
const state=reactive({ headerTitle: 'header标题' })
//Father:
<Child>
<template v-slot : header='scope'>
{{scope.title}}
</template>
<template v-slot : [state.slotName]>
动态插槽内容
</template>
<Child>
const state=reactive({ slotname: 'footer' })
…
//Father:
插槽内容
<template v-slot : [state.slotName]>
动态插槽内容
const state=reactive({ slotname: ‘footer’ })
作用:可以将父组件的内容动态渲染到到子组件插槽接口中。
## 四、插槽传值
```js
//Child:
<slot :title='state.headerTitle 'name='header'></slot>
...
<slot name='footer'></slot>
const state=reactive({ headerTitle: 'header标题' })
//Father:
<Child>
<template v-slot : header='scope'>
{{scope.title}}
</template>
<template v-slot : [state.slotName]>
动态插槽内容
</template>
<Child>
const state=reactive({ slotname: 'footer' })
父组件中的子标签中的v-slot:header="scope"
,scope
可以读取到名字为header
插槽上所有的属性,比如我们在子组件slot中声明的title
属性。同样利用这个方法,可以传递多个属性到父组件的插槽内容中