Vue3全家桶实战
一、Vue3和TS开发
Vue3全家桶实战:
Vue3基础语法+TypeScript+VueRouter+Pinia+axios+echarts
在Vue3开发中,提供了两种模式:
- Vue+JS来进行开发
- Vue+TS来进行开发:尽量选中这个版本
官方文档:cn.vuejs.org/
二、Vue3基本概念
Vue3框架是全新的框架。并不简单是Vue2升级版本
底层进行代码重构。在开发模式上提供了两种
- 选项是API:其实就是Vue2的开始模式,提供完整的组件模块,需要用什么,就写对应模块。
- 组合式API:Vue3比较常用的一种开发模式,将所有的基础模块都封装为了函数。引入函数来进行页面功能设计(提供一系列API用于开发)
Vue3优势:
- 源码包比较小:重构了底层的代码,进行了代码优化。在你项目中引入Vue3并不会导致项目非常庞大臃肿
- 底层代码采用TS设计:Vue3对TS的支持天然友好。一般我们尽量配合TS来使用
- 重写虚拟DOM,在性能上面有更好的表现
- Vite打包工具配合,会让Vue3项目开发、编译、打包更加流畅
三、创建项目
Vue3的项目创建有两种方式:
- 基于Webpack封装Vue脚手架 vue-cli(Vue2设计的)。相当于基于webpack来构造Vue3项目
- 采用Vite打包工具架来搭建Vue3项目
Vite是跟Vue3一起发布的一个打包工具,作用就是webpack一样。
在学习Vue3的时候,如果需要进行项目配置,研究Vite这个打包工具配置。
(1)创建项目
ts
npm create vite@latest
创建项目完成过后
ts
cd 项目
npm install
npm run dev
(2)打开项目
创建是TS项目,项目目录里面会有tsconfig.json文件
这个文件是TS项目描述文件,用于设置TS的加载、编译规则等等。
找到tsconfig.json
tsx
/* Bundler mode */
"moduleResolution": "Node", //bundler
// "allowImportingTsExtensions": true,
moduleResolution:
bundler这是默认值,代表模块的解析策略。如果值
bundler代表冲打包好的文件中链接资源。我们想要让他在开发中去寻找资源
Node
关于tsconfig.json文件中更多配置
ts文档:www.tslang.cn/docs/handbo...
在tsconfig.node.json文件中配置
ts
"moduleResolution": "Node",
(3)关于页面区别
tsx
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<h2>这是Vue3</h2>
<p>这是测试代码</p>
<HelloWorld></HelloWorld>
</template>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>
Vue3和Vue2在组件中分析到区别:
- style标签默认无法使用scss,需要自己安装scss并配置scss环境
- template标签不在约束我们只有一个根元素,可以允许多个模块一起渲染
- script身上默认setup(代表支持组合式API语法)ts(支持ts的语法)
- script标签中引入组件,无需注册,直接可以页面中使用
四、Vue插件
(1)Vue插件安装
注意:最好把Vue2的插件卸载(禁用)了。不要可能造成冲突。
(2)快速模板
在模板中填入下面的代码
ts
{
"Print to console": {
"prefix": "vue3",
"body": [
"<template>",
" <div></div>",
"</template>",
"",
"<script lang='ts' setup>",
"</script>",
"",
"<style lang='scss' scoped>",
"</style>"
],
"description": "Log output to console"
}
}
在组件中输入vue3,就提示你使用自定义模块创建页面
ts
vue3
五、设置路径别名
我们在项目开发过程中,会有很多文件会涉及相互引用
ts
import HelloWord from "../components/xxxx"
import {} from "../../api/xx"
找某个资源的时候,需要用到相对路径。文件目录结构比较复杂,相对路径写起来也比较麻烦
在Vue中你们可以直接@
来表示src目录import xxx from "@/apis/xxx"
在Vue项目项目中默认不支持@
, 需要自己配置这个路径
想要用@来代替src目录。实际需要自己找到项目的src的决定路径,用@符号来命名
需要获取到项目在本地磁盘中的绝对路径。只能通过Nodejs来获取
在TS文件中引入JS文件默认会报错。
在Vite.config.ts中引入path模块。默认会报错
ts
import path from "path"
解决在TS中引入JS代码报错的问题
- 将JS代码改为TS代码在引入
- 提供一个声明文件,不改变JS代码。额外创建一个声明文件(xxx.d.ts)。将js中变量、函数等等,提供说明。
(1)打开vite.config.ts文件
tsx
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from "path"
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
})
需要用到path模块来实现本地项目路径的获取。
下载依赖:目前nodejs里面所有代码都是js代码。ts项目中需要提供声明文件
ts
npm i @types/node --save-dev
(2)配置别名
tsx
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from "path"
//__dirname是Node环境中全局变量。代表获取当前文件所在文件夹
const pathSrc = path.resolve(__dirname,"src")
export default defineConfig(()=>{
return {
resolve:{
alias:{
"@":pathSrc
}
},
plugins:[vue()]
}
})
在组件中
tsx
<script setup lang="ts">
import Header from "@/components/Header.vue"
</script>
(3)设置TS的配置
在ts.config.json文件中配置代码如下
tsx
compilerOptions:{
....
"baseUrl": "/",
"paths": {
"@/*":["src/*"]
}
}
你在vite中配置了@符号,代码打包能是被。但是TS文件无法识别@符号具体代表什么意思。
六、安装自动导入
(1)自动导入概念
在Vue3项目中导入自定义组件还有组合式API来进行开发
tsx
import Header from "@/components/Header.vue"
<template>
<Header></Header>
</template>
实际上这个导入的过程是可以省略的。
ts
<template>
<Header></Header>
</template>
自动导入配置,可以减少我们import语句的设计。直接使用。
在Vue中开发我们采用组合式API
ts
<script>
import {ref,onMounted} from "vue"
const count = ref(0)
onMounted()
</script>
项目配置自动导入过后,省略import语句
ts
<script>
const count = ref(0)
onMounted()
</script>
并不是无需导入,实际上自动导入。所有API都自动导入,页面中可以直接使用。
(2)配置流程
下载插件
ts
npm install -D unplugin-auto-import unplugin-vue-components
下载两个插件,一个负责自动导入API,一个负责自动导入组件
在src 创建一个types文件夹
等会配置自动导入的规则后,默认在这个文件夹下面生成自动导入的代码
(3)vite.config.ts配置
tsx
import vue from '@vitejs/plugin-vue'
import { UserConfig, ConfigEnv, loadEnv, defineConfig } from "vite";
//配置路径别名
import path from "path";
const pathSrc = path.resolve(__dirname, "src");
//自动导入插件
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
return {
resolve: {
alias: {
"@": pathSrc,
},
},
plugins: [
vue(), //创建官方配置
AutoImport({
// 自动导入 Vue 相关函数,如:ref, reactive, toRef 等
imports: ["vue"],
eslintrc: {
enabled: true, // 是否自动生成 eslint 规则,建议生成之后设置 false
filepath: "./.eslintrc-auto-import.json", // 指定自动导入函数 eslint 规则的文件
},
dts: path.resolve(pathSrc, "types", "auto-imports.d.ts"), // 指定自动导入函数TS类型声明文件路径
}),
Components({
dts: path.resolve(pathSrc, "types", "components.d.ts"), // 指定自动导入组件TS类型声明文件路径
}),
]
}
})
下载的插件,引入过后我们需要在项目中进行配置。
AutoImport配置针对组合式API的自动导入
Components配置完成后主要是组件自动挡导入
七、Scss配置
Vue3项目创建的时候,默认没有下载Scss相关的依赖。
也没有配置scss的解析器
需要自己下载scss并配置
(1) 下载依赖
ts
npm install -D sass
(2)在src目录下面创建styles
ts
src/styles/全局scss文件
这个文件可以配置全局scss的环境。
创建variables.scss文件
tsx
$bg-color:pink
(3)vite.config.js中配置scss
项目打包的时候,如果要解析scss文件,需要配置对应规则
ts
css:{
preprocessorOptions:{
scss:{
javascriptEnabled:true,
additionalData:`@use "@/styles/variables.scss" as *`
}
}
}
配置完成这个内容过后,项目中就可以支持scss编程。
并且我们还设计一个全局的文件。以后在这个全局文件中定义的变量、样式可以在其他页面使用
(4)使用scss
ts
<template>
<div class="box">Content--{{count}}</div>
</template>
<script lang='ts' setup>
// import {ref} from "vue"
const count = ref(0)
</script>
<style lang="scss" scoped>
.box{
width:200px;
height:200px;
background-color:$bg-color
}
</style>
八、下载ElementPlus库
ElementPlus是专门给Vue3设计UI组件库。
(1)下载依赖
ts
npm install element-plus --save
(2)引入组件
在vite.config.ts文件中配置ElementPlus加载
ts
import vue from '@vitejs/plugin-vue'
import { UserConfig, ConfigEnv, loadEnv, defineConfig } from "vite";
//配置路径别名
import path from "path";
const pathSrc = path.resolve(__dirname, "src");
//自动导入插件
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
return {
resolve: {
alias: {
"@": pathSrc,
},
},
plugins: [
vue(), //创建官方配置
AutoImport({
// 自动导入 Vue 相关函数,如:ref, reactive, toRef 等
imports: ["vue"],
eslintrc: {
enabled: true, // 是否自动生成 eslint 规则,建议生成之后设置 false
filepath: "./.eslintrc-auto-import.json", // 指定自动导入函数 eslint 规则的文件
},
dts: path.resolve(pathSrc, "types", "auto-imports.d.ts"), // 指定自动导入函数TS类型声明文件路径
resolvers: [ElementPlusResolver()],
}),
Components({
dts: path.resolve(pathSrc, "types", "components.d.ts"), // 指定自动导入组件TS类型声明文件路径
resolvers: [ElementPlusResolver()],
}),
],
css:{
preprocessorOptions:{
scss:{
javascriptEnabled:true,
additionalData:`@use "@/styles/variables.scss" as *;`
}
}
}
}
})
配置完成过后,在项目中引入
ts
<template>
<div class="box">Content--{{count}}</div>
<el-button type="primary">Primary</el-button>
<el-button type="success">Success</el-button>
<el-button type="info">Info</el-button>
<el-button type="warning">Warning</el-button>
<el-button type="danger">Danger</el-button>
</template>
当你项目中用到了el-button这个组件。
我们在启动项目打包的时候,热部署的时候,寻找这个组件,并自动导入到项目中。
还会找到这个组件css代码,也导入项目中。
优势:你用到哪些组件,打包哪些组件,包括css样式。
就不会出现全局导入的情况,不管用不用的组件都打包
九、配置环境变量
在企业项目开发过程中,会对项目进行环境的设置
- 开发环境
- 生产环境
- 测试环境
每一种环境其实可能对应不同的服务器,不同端口,不同请求访问URL地址。
可以通过环境变量配置不同的参数,打包过程中可以根据这些参数来进行打包配置。
一个命令搞定你们项目各种打包环境
Vue3项目配置环境变量,需要参考Vite官方的一些配置
(1)创建环境变量文件
在项目根目录下面会创建多个.env
文件
先配置两个环境
.env.development
:代表开发环境,如果你启动项目采用npm run dev
默认会加载这个文件。里面配置变量可以拿到你们源码中使用
.env.production
:代表生产环境,你的项目通过npm run build
的时候默认加载的文件。里面的配置变量可以拿到源码中使用
如果你项目中还有.env
文件,优先级会比前面两个文件更低。只有环境中找不到具体的配置,默认使用这个文件来加载变量
(2)配置环境变量内容
我们可以将代码中需要配置的变量,可以定义在.env.[mode]
文件中。
启动项目,源码可以读取里面变量。去使用。以后要修改变量不要修改源码。直接修改配置文件。
在Vite官方,要求我们环境变量配置以 Vite_
来定义。
.env.development
开发环境配置:
ts
# 代表项目名字
VITE_APP_TITLE = "vite-project"
# 项目的端口
VITE_APP_PORT = 5555
# 项目的基础访问路径
VITE_BASE_URL_API = "/api"
# 接口服务器的访问地址
VITE_SERVER_PATH = "http://web.woniulab.com:8082"
.env.production
生产环境:
ts
VITE_APP_TITLE = "vite-project"
VITE_BASE_URL_API = "/api"
环境变量的作用:
- 你自己的源代码中可以通过
import.meta.env
来获取这个文件中变量信息 - 你可以在Vite的vite.config.ts文件中获取这个配置信息。改变端口、代理服务器配置
十、前端代理服务器
(1)关于跨域问题
跨域是因为浏览器的同源策略(安全策略)导致的。
什么情况才会引起跨域:请求协议不同、请求域名不同(ip)、请求端口不同
跨域报错信息
注意:
只有浏览器才会跨域报错。
手机App、小程序都没有跨域问题
(2)解决方案
(3)jsonp跨域
jsonp:利用了script标签支持跨域的特性。利用他来发送请求。这样绕过浏览器检测
ts
<script src="http://127.0.0.1:8002/users/getAccountList?callBack = myfunction">
jsonp这种解决方案,只能处理get请求。post无法解决
(4)后端配置CORS
ts
app.use(function(req,res,next){
res.setHeader("Access-Control-Allow-Origin","*");
res.setHeader("Access-Control-Allow-Headers","content-type,token,x-requested-with");
res.setHeader('Access-Control-Allow-Methods',"DELETE、GET、POST")
next();
});
后端跨域的原理:
前端如果发送请求直接到后端服务器。浏览器会发送两次请求
a. 第一次浏览器默认会发送一个options请求,去服务器验证,看服务器是否允许跨域访问。第一次发送请求,进入CORS配置,后端服务器将允许跨域的标识符,通过响应头返回给浏览器。
b. 当第一次请求拿到服务器允许访问的凭证。浏览器正式发送你的请求到服务器。
(5)前端跨域
前端解决跨域问题,通常采用代理服务器。
浏览器和服务器通信会有跨域问题。服务器和服务器通信不存在跨域问题
你前端发送请求,到自己前端服务器。前端服务器在将请求转发给后端服务器。浏览器发现不了我们其实跨服务器访问资源。
前端采用代理服务器来解决跨域的问题
流程如下:
前端浏览器发送请求前端服务器,如果访问静态资源,直接前端服务器返回静态资源
前端浏览器里面HTML中代码,要访问数据接口,也会进入前端服务器,
数据访问请求进入代理服务器,代理服务器负责去后后端进行通信拿到数据
代理服务器在将数据返回浏览器
前端发送请求的时候,需要给每个请求添加一个/api
符号
ts
import axios from "axios"
const newIntance = axios.create({
baseURL:"/api",
timeout:5000
})
export default newIntance
baseURL属性设置的是代理服务器的验证标志。
前端vite.config.ts文件
ts
import vue from '@vitejs/plugin-vue'
import { UserConfig, ConfigEnv, loadEnv, defineConfig } from "vite";
//配置路径别名
import path from "path";
const pathSrc = path.resolve(__dirname, "src");
//自动导入插件
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
return {
resolve: {
},
plugins: [
],
css:{
},
server:{
port:5555,
open:true,
proxy:{
"/api":{
//http://127.0.0.1:8002/users/getAccountList2
//现在浏览器传递地址:http://127.0.0.1:8002/api/users/getAccountList2
target:"http://127.0.0.1:8002",
changeOrigin:true,
rewrite:(path)=>path.replace(new RegExp("^/api"),"")
}
}
}
}
})
server代表前端服务器配置
port:代表前端服务器端口
open:前端服务期启动完成默认打开浏览器
proxy:前端服务器内置的代理服务器。一直都有,只是看你用不用
target:代理服务器访问地址。后端接口服务器地址
changeOrigin:开启跨域一些配置
rewrite:对请求路径进行重写
十一、使用环境变量
(1)优化配置
在vite.config.ts
文件中引入环境变量,并配置给server
ts
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
//加载环境变量
const env = loadEnv(mode,process.cwd())
server:{
port: Number(env.VITE_APP_PORT),
open:true,
proxy:{
[env.VITE_BASE_URL_API]:{
//http://127.0.0.1:8002/users/getAccountList2
//现在浏览器传递地址:http://127.0.0.1:8002/api/users/getAccountList2
target:env.VITE_SERVER_PATH,
changeOrigin:true,
rewrite:(path)=>path.replace(new RegExp("^"+env.VITE_BASE_URL_API),"")
}
}
}
}
还需要在axios的工具中配置BaseURL
ts
import axios from "axios"
const newIntance = axios.create({
baseURL:import.meta.env.VITE_BASE_URL_API,
timeout:5000
})
export default newIntance
(2)打包部署
项目开发完成后,你提交给别人部署的文件一定是静态资源文件。
前端配置跨域、代理服务器都没有用了。
前端打包后资源如果要部署到服务器,我们一般会选中用nginx服务器。
读取.env.production
文件
nginx的配置
html文件夹里面存放你项目代码
nginx中nginx.conf存放配置
ts
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
try_files $uri /index.html;
}
location /apis/ {
rewrite ^.+apis/?(.*)$ /$1 break;
proxy_pass http://127.0.0.1:8002;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
expires 1d;
}
}
第一个location代表访问nginx服务器默认加载文件是谁。
第二个location代表配置代理服务器。当检测到请求路径中包含/apis
,可以进入代理服务器。
负责代理转发请求给指定proxy服务器。获取数据
十二、路由搭建
Vite创建Vue3项目,默认没有大家好路由配置
需要自己下载路由插件,并配置路由文件
(1)下载路由
ts
npm i vue-router@next
(2)创建路由文件
在src/router/index.ts
中配置代码如下
ts
import {createRouter,createWebHistory,RouteRecordRaw} from "vue-router"
import Login from "../views/Login.vue"
import Register from "../views/Register.vue"
import Home from "../views/Home.vue"
const routes:Array<RouteRecordRaw> = [
{path:"/login",name:"Login",component:Login},
{path:"/reg",component:Register},
{path:"/home",component:Home}
]
//创建一个路由对象
const router = createRouter({
routes,
history:createWebHistory()
})
export default router
总结:
路由模式设计:history属性来表示路由模式设计,值采用函数来表示。
定义路由映射的时候,需要对路由进行类型约束,RouteRecordRaw这个约束来使用
(3)加载路由配置
在main.ts文件中,引入路由配置文件,并加载插件
ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from "@/router"
const app = createApp(App)
//app实例上面挂载插件,以后每个组件都可以使用router这个插件
app.use(router)
app.mount('#app')
完成这个步骤后,VueRouter就实现了全局挂载
以后在任何一个组件中,都可以直接使用路由
设置路由渲染出口。
在App.vue中
ts
<template>
<div>
<router-view></router-view>
</div>
</template>
<script lang='ts' setup>
</script>
<style lang='scss' scoped>
</style>
(4)组件中使用路由
ts
<template>
<div>Login</div>
<button @click="gotoRegister">按钮</button>
<router-link to="/reg">没有注册去注册</router-link>
</template>
<script lang='ts' setup>
import {useRouter} from "vue-router"
const $router = useRouter()
const gotoRegister = ()=>{
$router.push({
path:"/reg",
query:{id:1}
})
}
</script>
<style lang='scss' scoped>
</style>
组件二
ts
<template>
<div>注册</div>
</template>
<script lang='ts' setup>
import {useRoute} from "vue-router";
const $route = useRoute();
//组件挂载完毕
onMounted(() => {
console.log($route);
console.log($route.query);
console.log($route.params);
console.log($route.path);
})
</script>
<style lang='scss' scoped>
</style>
总结:
$router的结果来自于组合式API中useRouter
$route的结果来自于子河是API中useRoute
这种开发模式,典型的组合式API,需要什么功能,引入什么样函数
(5)404的配置
ts
const routes:Array<RouteRecordRaw> = [
{path:"/login",name:"Login",component:Login},
{path:"/reg",component:Register},
{path:"/home",component:Home,children:[
{path:"system/user",component:User},
{path:"system/role",component:Role}
],
},
{path:"/:pathMatch(.*)",redirect:"/404"},
{path:"/404",component:NotFind}
]
总结:
Vue3中配置路由无法支持*来匹配
需要采用pathMatch来进行路由检索。当无法匹配默认进入pathMatch进行匹配,在进行重定向
十三、组合式API
在Vue3进行页面开发和设计
会采用组合式API的方式来设计。
将常用的组合式API进行分类。
(1)定义组件内部数据ref
ts
<template>
<div>
<h3>登录页面</h3>
<p>{{count}}</p>
<button @click="updateData">修改count</button>
<p>{{user}}</p>
<button @click="updateUser">修改user</button>
<p>{{students}}</p>
<button @click="updateStudent">新增一个</button>
</div>
</template>
<script lang='ts' setup>
import {ref} from "vue"
import {IUser} from "@/interfaces/userInterface"
//页面上的数据,可以拆分开独立使用,独立维护
const count = ref<number>(0)
const user = ref<IUser>({id:1,name:"xiaowang",address:"南京"})
const students = ref<Array<number>>([1,2,3])
/**
* 函数列表
*/
const updateData = ()=>{
console.log(count);
count.value = 100
}
const updateUser = ()=>{
user.value.name = "xiaofeifei"
}
const updateStudent = ()=>{
students.value[students.value.length] = 4
}
</script>
<style lang='scss' scoped>
</style>
总结:
ref可以定义组件内部数据。
页面每个数据,都可以用ref单独设计出来。独立维护和更新
template标签中使用直接用ref的变量
script标签要使用ref变量,必须采用value来进行操作
(2)定义组件内部数据Reactive
ts
<template>
<div>
<h3>登录页面</h3>
<p>{{count}}</p>
<button @click="updateData">修改count</button>
<p>{{user}}</p>
<button @click="updateUser">修改user</button>
<p>{{students}}</p>
<button @click="updateStudent">新增一个</button>
<p>{{obj}}</p>
<button @click="updateReactive">修改Reactive</button>
</div>
</template>
<script lang='ts' setup>
import {ref,reactive} from "vue"
import {IUser,IReactive} from "@/interfaces/userInterface"
//页面上的数据,可以拆分开独立使用,独立维护
const count = ref<number>(0)
const user = ref<IUser>({id:1,name:"xiaowang",address:"南京"})
const students = ref<Array<number>>([1,2,3])
const obj = reactive<IReactive>({
myuser:{id:1},
age:10
})
/**
* 函数列表
*/
const updateData = ()=>{
console.log(count);
count.value = 100
}
const updateUser = ()=>{
user.value.name = "xiaofeifei"
}
const updateStudent = ()=>{
students.value[students.value.length] = 4
}
const updateReactive = ()=>{
obj.age = 80
}
</script>
<style lang='scss' scoped>
</style>
总结:
reactive默认定义的是一个对象,在里面可以存放很多个数据结构。
reactive在进行更新的时候,直接reactive变量·属性。不需要在找到value属性
reactive适合用于复杂类型数据。
(3)计算属性
在Vue2中选项式API。可以直接用computed模块表示
ts
computed:{
newData(){
return xxx
}
}
在Vue3里面也是组合式API的开发
ts
const count = ref<number>(0)
//计算属性
const newData = computed(()=>{
console.log("计算属性");
return count.value * 3
})
总结:
计算属性特点:
- 计算属性一定要在函数里面return结果。这样才可以在外面得到最终数据
- 计算属性内部用到的变量发生变化,才会重新执行一次
- count.value * 3,计算count.value属性。并监控count这个变量
(4)watch侦听器
ts
//watch侦听器
//第一个参数是函数,返回什么数据,侦听什么数据
//第二个参数是函数,侦听过程
//第三个参数是对象,设置侦听配置
watch(
()=>count,
(newValue,oldValue)=>{
console.log("newValue",newValue);
console.log("oldValue",oldValue);
},
{
immediate:true,
deep:true
}
)
关于侦听器目前提供的组合式API,需要通过传递参数的形式来控制侦听过程
总结:
第一个函数返回值是谁,侦听到结果就是谁
可以在第二个函数里面得到侦听之前结果和侦听之后结果
第三个参数,可选项,可以不写
(5)watchEffect侦听器
Vue3提供的一种新的侦听器。
语法更加简单。执行一段副作用。
ts
watchEffect(()=>{
console.log("watchEffect");
console.log(count.value);
console.log(user.value.name);
})
当函数里面使用了对应变量,值发生变化,就会跟着执行。
十四、生命周期
Vue3中声明周期只有三个阶段:
- 挂载阶段
- 更新阶段
- 销毁阶段
API名字 | 含义 |
---|---|
onBeforeMount | 被挂载之前 |
onMounted | 挂载完成 |
onBeforeUpdate | 更新之前 |
onUpdated | 更新之后 |
onBeforeUnmount | 组件销毁之前 |
onUnmounted | 组件销毁 |
onActivated | 被keepalive组件包裹,进入组件 |
onDeactivated | 被keepalive组件包裹,离开组件 |
十五、组件通信
在Vue3中父子组件通信严格按照TS标准来进约束
所以在开发过程中比Vue2稍微麻烦一点,涉及到数据约束
父传子
子组件定义约束条件
ts
<template>
<div>
<h2>{{props.msg}}</h2>
<span v-for="(item,index) in props.list" :key="item">{{item}}{{props.list.length-1==index?"":"/"}}</span>
</div>
</template>
<script lang='ts' setup>
//通过defineProps这个api我们可以约束父组件传递过来参数
const props = defineProps<{msg:string,list:Array<string>}>()
</script>
<style lang='scss' scoped>
</style>
子组件定义约束条件,通过defineProps
进行约束。
如果指定的参数,可传可不传递,可以使用?
例如:{msg:string,list?:Array<string>}
父组件传递参数
ts
<template>
<div>Main</div>
<MyBreadnav msg="组件通信" :list="list"></MyBreadnav>
</template>
<script lang='ts' setup>
import {ref} from "vue"
const list = ref<Array<string>>(["首页","系统管理","用户管理2"])
</script>
<style lang='scss' scoped>
</style>
静态的参数,和动态参数都可以作为组件身上的变量传递过去。
子传父
子组件传递参数给父组件,也是通过自定义事件的方式来传递
子组件定义自定义事件的名字。
子组件定义约束条件
ts
<template>
<div>
<h2>{{msg}}</h2>
<span @click="checkedSpan" v-for="(item,index) in props.list" :key="item">{{item}}{{props.list.length-1==index?"":"/"}}</span>
</div>
</template>
<script lang='ts' setup>
//通过defineProps这个api我们可以约束父组件传递过来参数
const props = defineProps<{msg:string,list:Array<string>}>()
//约束了两个自定义事件名字
const $emit = defineEmits<{
(e:"changeSpan",val:string):void,
(e:"changeTitle",val:string):void,
}>()
const checkedSpan = ()=>{
$emit("changeSpan","角色管理")
}
</script>
<style lang='scss' scoped>
</style>
其中defineEmits
属于内置API,无需引入直接使用,用于定义自定义事件名字。
约束了子组件触发自定义事件的时候,名字约定好的名字。
约束了父组件传递自定义事件的时候,必须按照子组件给定要求。否则编译报错
十六、自定义指令
整理一下目前常见指令
v-text
、v-html
、v-for
、v-if
、v-show
、v-clock
、v-model
等等
还提供自定义指令。
自定义指定可以将你自己一些公共业务封装起来。在组件中一键渲染
v-model底层代码(语法糖)
ts
data(){
return {
value:"123"
}
}
<input :value="value" v-bind:input="event=>this.value = event.target.value">
当你在项目中发现很多不熟悉的指令。你要思考是否为公司自己封装指令。
全局指令和局部指
全局指令
指的是在全局对象中定义指令,可以在任何组件中使用。
(1)在main.ts文件中定义
ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from "@/router"
const app = createApp(App)
//定义全局指令 onblur onfocus
//vue自定义指令都必须 v-xxx开始设计
app.directive("focus", (el, binding) => {
//直接让当前这个元素获取焦点
el.focus()
})
app.directive("bgColor",(el,binding)=>{
console.log(binding);
el.style.backgroundColor = binding.value
})
//app实例上面挂载插件,以后每个组件都可以使用router这个插件
app.use(router)
app.mount('#app')
需要将指令设置在App组件上面,任何一个子组件都可以使用这个指令。
参数:
- el:代表绑定的元素节点
- binding:绑定指令的时候,传递的参数
局部指令
在组件中定义指令,只能在本组件中使用。不能跨组件使用
局部指令在Vue3中,必须按照官方标准来定义名字。
ts
<input type="text" v-focus2>
//定义一个对象,名字必须v开头,驼峰命名
const vFocus2 = {
//指令的生命周期,代表这个指定绑定节点被挂载
mounted(el,binding){
el.focus()
}
}
在指定的组件中定义局部指令,命名必须按照官方标准。
这个指令就可以被当前组件使用
局部指令比较多,将局部指令提取出去,统一管理
在src/directives/index.ts
ts
export const vFocus2 = {
//指令的生命周期,代表这个指定绑定节点被挂载
mounted(el:any){
el.focus()
}
}
export const vBgcolor = {
mounted(el:any){
el.style.backgroundColor = "red"
}
}
在组件中引入
ts
import {vFocus2,vBgcolor} from "@/directives"
<input v-bgcolor>
可以用自定义指令来实现按钮权限。可以统一处理页面中按钮的显示和隐藏
Vue2的过滤器在Vue3里面没有了,被回收了
ts
{{msg | filterData}}
十七、国际化配置
一套系统可以实现多语言切换
国际化概念:一套系统涉及到多种语言切换。一般适用于跨国系统
我们可以自己配置多种语言列表,用户自己选中切换,也会根据系统来决定访问语言
(1)下载插件
插件名字叫i18n,实际上单词internationalization
ts
npm install vue-i18n@9
(2)自定义语言包
在项目中要将切换的语言(内容)提取出来,放在文件中,默认加载这些文件
以前在项目中写死的中文或者英文提示,现在全都需要放在文件中。页面没有写死,通过文件加载显示内容。可以做到切换文件实现加载不同语言
目前我们项目中,设计两个语言包
中文:zh-cn.ts
英文:en.ts
在src/lang/package中分别创建两个语言包文件
zh-cn.ts
和en.ts
文件中内容
tsx
export default {
login:{
title:"管理系统",
username:"用户名",
password:"密码",
btn:"登录",
link:"没有注册?去注册"
}
}
ts
export default {
login:{
title:" System",
username:"username",
password:"password",
btn:"login",
link:"no register?goto register"
}
}
(3)创建i18n实例
创建i18n对象,加载我们语言包
src/lang/index.ts
代表国际化入口文件
ts
import {createI18n} from "vue-i18n"
import enLocal from "./package/en"
import zhCnLocal from "./package/zh-cn"
//定义一个对象,实现不同语言包,设置不同名字
const message = {
en:{
...enLocal
},
"zh-cn":{
...zhCnLocal
}
}
//创建i18n实例,并默认设置加载哪个语言包
const i18n = createI18n({
locale:"zh-cn", //这个初始值可以从操作系统获取
message,
legacy: false
})
export default i18n
总结:
- locale的值决定了第一次进来,我们加载哪个语言包。这个值可以写死。也可以根据服务器或者操作系统的语言环境来决定。
- 后续如果设置locale的值,也可以实现切换不同语言包
(4)在main.ts中加载文件
ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from "@/router"
import i18n from "@/lang"
const app = createApp(App)
//定义全局指令 onblur onfocus
//vue自定义指令都必须 v-xxx开始设计
app.directive("focus", (el, binding) => {
//直接让当前这个元素获取焦点
el.focus()
})
app.directive("bgColor",(el,binding)=>{
console.log(binding);
el.style.backgroundColor = binding.value
})
//app实例上面挂载插件,以后每个组件都可以使用router这个插件
app.use(router)
app.use(i18n)
app.mount('#app')
需要在main.ts中引入对应的国际化实例,并挂载到Vue的实例上面。
以后任何一个组件,都可以使用国际化配置来实现页面渲染
(5)组件中引入国际化
ts
<template>
<h2>{{$t("login.title")}}</h2>
<div>
<input v-model="user.username" type="text" placeholder="用户名">
</div>
<div>
<input v-model="user.password" type="text" placeholder="密码">
</div>
<div>
<button @click="loginBtn">{{$t("login.btn")}}</button>
</div>
<button @click="changeLanguage">切换语言</button>
</template>
<script lang='ts' setup>
import {IUser2} from "@/inerfaces/userInerface"
import {loginApi} from "@/apis/userApi"
import {useRouter} from "vue-router"
import {useI18n} from "vue-i18n"
const $router = useRouter()
const {t,locale} = useI18n()
const changeLanguage = ()=>{
locale.value = "zh-cn"
}
</script>
<style lang='scss' scoped>
</style>
需要注意在组件中渲染静态文本,必须采用$t()
进行页面渲染。
改变当前环境变量,需要获取 locale
变量,并设置不同值
十八、pinia状态机
来源:Vue3的组合式API作为核心编程规范,伴随着状态机也需要升级。Vue官方单独设计了一个状态机pinia
这个状态机比Vuex更亲量化。开发用起来更加简单
但是pinia并不仅仅只是Vue3使用。Vue2中依然可以使用。
区别:
- Vuex适合中大型项目,业务分割比较完善。流程也比较完善
- pinia适合中小型项目。
- Vuex更加复杂,pinia更加亲量化。
环境搭建
(1)下载pinia
ts
npm i pinia
(2)在main.ts中加载pinia
pinia是属于官方的插件,一般都会将插件在入口文件中引入,并使用Vue来挂载这个插件
以后任何一个组件,都可以获取状态机
ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from "@/router"
import i18n from "@/lang"
import {createPinia} from "pinia"
const pinia = createPinia()
const app = createApp(App)
app.use(router)
app.use(i18n)
//加载插件
app.use(pinia)
app.mount('#app')
总结:
在项目中能够用app.use(存放内容),这就称为加载插件。
想要加载插件,你提供的模块必须是Vue官方认可的插件
作用:可以将插件的功能,绑定给App这个实例。
比如:app.use(i18n),在app上面绑定国际化配置,比如$t()
app.use(ElementUI) //ElementUI代表将所有功能和组件都挂载到app上面
(3)创建模块
在pinia中我们数据也会分模块。默认情况,一个业务就是一个模块
在src/store/userStore.ts
ts
import { defineStore } from "pinia"
import { findMenuByName } from "@/apis/menuApi"
//定义好了一个仓库对象。
//pinia状态机内部只有三个模块
//state:仓库数据
//getters:仓库计算属性
//actions:仓库行为
export const userStore = defineStore("userStore", {
state: () => {
return {
username: "xiaowang",
password: "123",
age: 23,
role: "管理员"
}
},
getters: {
fullName(state) {
return state.username.toUpperCase()
}
},
actions: {
increment(age: number) {
this.age = age
},
async increment2() {
const res = await findMenuByName("bobo")
console.log("menus", res.data.data);
}
}
})
总结:
- 只有三个核心模块,这是pinia简化过后结果
- 去掉mutations,将所有修改业务都整合在actions中
- actions可以包含同步代码以及异步代码
- 默认文件名字(结构)就已经模块化了。无需在引入主仓库来整合模块化
(4)组件中使用
组件中使用状态机,按照模块直接导入就可以使用
ts
<template>
<div>
<span>仓库的age:{{userStoreData.age}}</span>
<span>仓库的username:{{userStoreData.username}}</span>
<el-button @click="changeStoreAge">仓库的age修改</el-button>
<el-button @click="changeStoreDataAsync">异步修改</el-button>
</div>
</template>
<script lang='ts' setup>
//组合API
import {userStore} from "@/store/userStore"
const userStoreData = userStore()
console.log(userStoreData);
console.log(userStoreData.age);
console.log(userStoreData.username);
const changeStoreAge = ()=>{
userStoreData.increment(44)
}
const changeStoreDataAsync = ()=>{
userStoreData.increment2()
}
</script>
<style lang='scss' scoped>
</style>
十九、刷新pinia丢失数据
只要涉及状态机都会出现这个问题。
状态机数据存放在内存里面。
如果在一个组件中往状态机存放数据,在另外一个页面中刷新,数据丢失(数据重置)
需要给pinia下载一个插件,让pinia的数据能够持久化(将内存数据存储硬盘中)
原理:先会将数据存放到状态机里面,当检测到刷新的时候,将数据进行本地存储。下次操作数据的时候,会从本地将数据取出来,放回状态机
(1)下载插件
ts
npm install pinia-plugin-persist
这个插件是pinia提供的插件。需要在pinia中加载
(2)在pinia中加载插件
需要打开main.ts文件。引入pinia的时候,进行插件配置
ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from "@/router"
import i18n from "@/lang"
import {createPinia} from "pinia"
import piniaPersist from "pinia-plugin-persist"
const app = createApp(App)
const pinia = createPinia()
//创建pinia过后,我们需要给pinia设置插件。在去进行app.use(pinia)
pinia.use(piniaPersist)
//app实例上面挂载插件,以后每个组件都可以使用router这个插件
app.use(router)
app.use(i18n)
app.use(pinia)
app.mount('#app')
核心代码必须在app.user(pinia)代码执行之前,进行pinia的插件配置
(3)仓库中进行配置
开启持久化
ts
import { defineStore } from "pinia"
import { findMenuByName } from "@/apis/menuApi"
//定义好了一个仓库对象。
//pinia状态机内部只有三个模块
//state:仓库数据
//getters:仓库计算属性
//actions:仓库行为
export const userStore = defineStore("userStore", {
state: () => {
return {
username: "",
password: "123",
age: 23,
role: ""
}
},
getters: {
fullName(state) {
return state.username.toUpperCase()
}
},
actions: {
increment(age: number) {
this.age = age
},
async increment2() {
const res = await findMenuByName("bobo")
console.log("menus", res);
},
changeStoreUsername(username:string){
this.username = username
},
changeStoreRolename(roleName:string){
this.role = roleName
}
},
persist:{
enabled:true //开启持久化
}
})
默认将所有的仓库数据都持久化本地存储,本地存储的数据,默认存储在sessionStorage中
相当于项目运行期间,数据保存起来。当项目关闭的时候(会话结束)。删除刚刚存储的数据
(4)还可以指定哪些数据需要存储
ts
import { defineStore } from "pinia"
import { findMenuByName } from "@/apis/menuApi"
//定义好了一个仓库对象。
//pinia状态机内部只有三个模块
//state:仓库数据
//getters:仓库计算属性
//actions:仓库行为
export const userStore = defineStore("userStore", {
state: () => {
return {
username: "",
password: "123",
age: 23,
role: ""
}
},
getters: {
fullName(state) {
return state.username.toUpperCase()
}
},
actions: {
increment(age: number) {
this.age = age
},
async increment2() {
const res = await findMenuByName("bobo")
console.log("menus", res);
},
changeStoreUsername(username:string){
this.username = username
},
changeStoreRolename(roleName:string){
this.role = roleName
}
},
persist:{
//开启持久化
enabled:true,
//数据存储的策略
strategies:[
{
key:"user",
storage:sessionStorage,
paths:["username","role"]
}
]
}
})
可以配置存储数据的策略strategies
key:存放数据自定义键
storage:指定存储类型。默认localStorage和sessionStorage
paths:指定哪些属性需要初始化
二十、路由缓存
在进行路由渲染的时候,我们需要使用keepalive进行配合缓存。
(1)需要设置meta元信息
ts
const routes:Array<RouteRecordRaw> = [
{path:"/login",component:Login,meta:{cache:false}},
{path:"/reg",component:Register,meta:{cache:false}},
{path:"/",component:Home,meta:{cache:false},
children:[
{path:"home",component:Main,meta:{cache:false}},
{path:"system/user",component:User,meta:{cache:true}},
{path:"system/role",component:Role,meta:{cache:false}}
],
},
{path:"/:pathMatch(.*)",redirect:"/404",meta:{cache:false}},
{path:"/404",component:NotFind,meta:{cache:false}}
]
你就决定了哪些路由对应组件需要缓存。
(2)在路由渲染组件配置缓存
在Home中找到渲染出口。配置缓存
ts
import {useRoute} from "vue-router"
const route = useRoute()
这个route就包含了路由完整信息。{path,name,component,meta}
我需要取出meta的元信息进行页面判断
Vue2中,我们代码如下:
ts
<keep-alive>
<router-view v-if="route.meta.cache"></router-view>
</keep-alive>
<router-view v-if="!route.meta.cache"></router-view>
当检测meta里面cache=true,那就走keepalive这段代码,否则就走keepalive外面的代码
但是在Vue3规范中不允许我们将router-view这个组件放在keepalive组件中使用
Vue3的语法为:
ts
<router-view v-slot="{Component}">
<keep-alive>
<component v-if="route.meta.cache" :is="Component"></component>
</keep-alive>
<component v-if="!route.meta.cache" :is="Component"></component>
</router-view>
在进行设计的是时候,keepalive这个组件里面包含component,来进行动态组件渲染