vue3全家桶项目搭建

Vue3全家桶实战

一、Vue3和TS开发

Vue3全家桶实战:

Vue3基础语法+TypeScript+VueRouter+Pinia+axios+echarts

在Vue3开发中,提供了两种模式:

  1. Vue+JS来进行开发
  2. Vue+TS来进行开发:尽量选中这个版本

官方文档:cn.vuejs.org/

二、Vue3基本概念

Vue3框架是全新的框架。并不简单是Vue2升级版本

底层进行代码重构。在开发模式上提供了两种

  1. 选项是API:其实就是Vue2的开始模式,提供完整的组件模块,需要用什么,就写对应模块。
  2. 组合式API:Vue3比较常用的一种开发模式,将所有的基础模块都封装为了函数。引入函数来进行页面功能设计(提供一系列API用于开发)

Vue3优势:

  1. 源码包比较小:重构了底层的代码,进行了代码优化。在你项目中引入Vue3并不会导致项目非常庞大臃肿
  2. 底层代码采用TS设计:Vue3对TS的支持天然友好。一般我们尽量配合TS来使用
  3. 重写虚拟DOM,在性能上面有更好的表现
  4. Vite打包工具配合,会让Vue3项目开发、编译、打包更加流畅

三、创建项目

Vue3的项目创建有两种方式:

  1. 基于Webpack封装Vue脚手架 vue-cli(Vue2设计的)。相当于基于webpack来构造Vue3项目
  2. 采用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在组件中分析到区别:

  1. style标签默认无法使用scss,需要自己安装scss并配置scss环境
  2. template标签不在约束我们只有一个根元素,可以允许多个模块一起渲染
  3. script身上默认setup(代表支持组合式API语法)ts(支持ts的语法)
  4. 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代码报错的问题

  1. 将JS代码改为TS代码在引入
  2. 提供一个声明文件,不改变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样式。

就不会出现全局导入的情况,不管用不用的组件都打包

九、配置环境变量

在企业项目开发过程中,会对项目进行环境的设置

  1. 开发环境
  2. 生产环境
  3. 测试环境

每一种环境其实可能对应不同的服务器,不同端口,不同请求访问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"

环境变量的作用:

  1. 你自己的源代码中可以通过import.meta.env来获取这个文件中变量信息
  2. 你可以在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
})

总结:

计算属性特点:

  1. 计算属性一定要在函数里面return结果。这样才可以在外面得到最终数据
  2. 计算属性内部用到的变量发生变化,才会重新执行一次
  3. 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中声明周期只有三个阶段:

  1. 挂载阶段
  2. 更新阶段
  3. 销毁阶段
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-textv-htmlv-forv-ifv-showv-clockv-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组件上面,任何一个子组件都可以使用这个指令。

参数:

  1. el:代表绑定的元素节点
  2. 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.tsen.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

总结:

  1. locale的值决定了第一次进来,我们加载哪个语言包。这个值可以写死。也可以根据服务器或者操作系统的语言环境来决定。
  2. 后续如果设置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中依然可以使用。

区别:

  1. Vuex适合中大型项目,业务分割比较完善。流程也比较完善
  2. pinia适合中小型项目。
  3. 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);
        }
    }
})

总结:

  1. 只有三个核心模块,这是pinia简化过后结果
  2. 去掉mutations,将所有修改业务都整合在actions中
  3. actions可以包含同步代码以及异步代码
  4. 默认文件名字(结构)就已经模块化了。无需在引入主仓库来整合模块化

(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,来进行动态组件渲染

相关推荐
前端爆冲7 分钟前
项目中无用export的检测方案
前端
热爱编程的小曾35 分钟前
sqli-labs靶场 less 8
前端·数据库·less
gongzemin1 小时前
React 和 Vue3 在事件传递的区别
前端·vue.js·react.js
Apifox1 小时前
如何在 Apifox 中通过 Runner 运行包含云端数据库连接配置的测试场景
前端·后端·ci/cd
树上有只程序猿1 小时前
后端思维之高并发处理方案
前端
庸俗今天不摸鱼2 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
黄毛火烧雪下2 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox2 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞2 小时前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行2 小时前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox