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

相关推荐
ekskef_sef1 小时前
32岁前端干了8年,是继续做前端开发,还是转其它工作
前端
sunshine6411 小时前
【CSS】实现tag选中对钩样式
前端·css·css3
真滴book理喻2 小时前
Vue(四)
前端·javascript·vue.js
蜜獾云2 小时前
npm淘宝镜像
前端·npm·node.js
dz88i82 小时前
修改npm镜像源
前端·npm·node.js
Jiaberrr2 小时前
解锁 GitBook 的奥秘:从入门到精通之旅
前端·gitbook
顾平安3 小时前
Promise/A+ 规范 - 中文版本
前端
聚名网3 小时前
域名和服务器是什么?域名和服务器是什么关系?
服务器·前端
桃园码工3 小时前
4-Gin HTML 模板渲染 --[Gin 框架入门精讲与实战案例]
前端·html·gin·模板渲染
不是鱼3 小时前
构建React基础及理解与Vue的区别
前端·vue.js·react.js