Vue2 速升V3手册

2021-?-?

Vue 3

V3 官页

V3 官页文档

V3 官页API文档

文档 - 应用 API

文档 - 全局 API

uni-app 升级Vue3指南 - https://uniapp.dcloud.net.cn/tutorial/migration-to-vue3.html

Vue2 to Vue3 的改变

渲染函数

渲染函数 API 改变
$scopedSlots property 已删除, 所有插槽都通过 $slots 作为函数暴露

自定义指令 API 已更改为与组件生命周期一致

一些转换 class 被重命名了:
v-enter -> v-enter-from
v-leave -> v-leave-from

组件 watch 选项和实例方法 $watch 不再支持点分隔字符串路径, 请改用计算函数作为参数

在 Vue 2.x 中, 应用根容器的 outerHTML 将替换为根组件模板 (如果根组件没有模板/渲染选项, 则最终编译为模板); VUE3.x 现在使用应用程序容器的 innerHTML;终于不替换整个dom了

其他小改变

destroyed 生命周期选项被重命名为 unmounted
beforeDestroy 生命周期选项被重命名为 beforeUnmount
prop default 工厂函数不再有权访问 this 是上下文
自定义指令 API 已更改为与组件生命周期一致
data 应始终声明为函数
来自 mixin 的 data 选项现在可简单地合并
attribute 强制策略已更改
一些过渡 class 被重命名
组建 watch 选项和实例方法 $watch 不再支持以点分隔的字符串路径; 请改用计算属性函数作为参数;
<template> 没有特殊指令的标记 (v-if/else-if/else, v-for 或 v-slot) 现在被视为普通元素, 并将生成原生的 <template> 元素, 而不是渲染其内部内容;

在 Vue 2.x 中, 应用根容器的 outerHTML 将替换为根组件模板 (如果根组件没有模板/渲染选项, 则最终编译为模板); Vue 3.x 现在使用应用容器的 innerHTML, 这意味着容器本身不再被视为模板的一部分;

移除 API

keyCode 支持作为 v-on 的修饰符
$on, $off 和 $once 实例方法
过滤
内联模板 attribute

$destroy 实例方法; 用户不应再手动管理单个 Vue 组件的生命周期

hello-vue3

脚手架 vue-cli:

npm install -g @vue/cli  OR yarn global add @vue/cli
vue create hello-vue3
select vue 3 preset

组合式API

setup 的2个参数

setup-组件选项

新的 setup 组件选项在创建组件之前执行, 一旦 props 被解析, 并充当合成 API 的入口点;

由于在执行 setup 时尚未创建组件实例, 因此在 setup 选项中没有 this; 这意味着, 除了 props 之外, 你将无法访问组件中声明的任何属性——本地状态, 计算属性或方法;

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted } from 'vue'export default {setup (props) {//响应式变量const repositories = ref([])return {}//// 这里返回的任何内容都可以用于组件的其余部分}// 组件的"其余部分" data 等
}

从 setup 返回的所有内容都将暴露给组件的其余部分 (计算属性, 方法, 生命周期钩子等等) 以及组件的模板;

in short 增加 setup 选项,作为入口点, 其返回的属性, 均会作为'组件选项属性'

由于在执行 setup 时尚未创建组件实例, 因此在 setup 选项中没有 this; 这意味着, 除了 props 之外, 你将无法访问组件中声明的任何属性——本地状态, 计算属性或方法;

因此, 你只能访问以下 property:
props,attrs,slots, emit

换句话说, 你将无法访问以下组件选项:
data,computed, methods

props

第一个参数是 props
setup 函数中的第一个参数是 props: 正如在一个标准组件中所期望的那样, setup 函数中的 props 是响应式的, 当传入新的 prop 时, 它将被更新;

export default {props: {modelValue: String //v-model 的用法, 默认是 modelValue, 以前是`value: String`},setup(props, context) {// props (响应式对象)console.log(props)// 可以拿到 修饰符console.log(props.modelModifiers) // { capitalize: true } 即 <MyComponent v-model.capitalize="myText" />}
}

v-model 的用法更新
context.emit('update:modelValue', title) // 以前是 this.$emit('input', title)

完整写法: <MyComponent v-model:modelValue.capitalize="myText">

context

第二个参数是 context
传递给 setup 函数的第二个参数是 context; context 是一个普通的 JavaScript 对象, 它暴露三个组件的 property:

export default {props: {modelValue: String //v-model 的用法, modelValue 可以自定义, 以前是`value: String`},setup(props, context) {// Attribute (非响应式对象)console.log(context.attrs)//透传属性? 见下// 插槽 (非响应式对象)console.log(context.slots)// 触发事件 (方法) // context.emit('compent-event')console.log(context.emit)}
}
插槽&具名插槽

具名插槽:
BaseLayout.vue

<div class="container"><header><slot name="header"></slot></header><main><slot></slot></main><footer><slot name="footer"></slot></footer>
</div>

没有提供 name 的 <slot> 出口会隐式地命名为“default”。

在父组件中使用时,我们需要一种方式将多个插槽内容传入到各自目标插槽的出口。此时就需要用到具名插槽了
v-slot 有对应的简写#,因此 <template v-slot:header> 可以简写为 <template #header>

<BaseLayout><template v-slot:header="headerProps"><!-- header 插槽的内容放这里 --></template>
</BaseLayout>

props 传递

向具名插槽中传入 props:
<slot name="header" message="hello"></slot>

接受

<template #header="headerProps">{{ headerProps }}</template>

注意插槽上的 name 是一个 Vue 特别保留的 attribute,不会作为 props 传递给插槽。因此最终 headerProps 的结果是 { message: 'hello' }

定义响应式变量

在 Vue 3.0 中, 我们可以通过一个新的 ref 函数使任何响应式变量在任何地方起作用
ref 接受参数并返回它包装在具有 value property 的对象中, 然后可以使用该 property 访问或更改响应式变量的值:

import { ref } from 'vue'const counter = ref(0)console.log(counter) // { value: 0 }
console.log(counter.value) // 0counter.value++
console.log(counter.value) // 1

对 对象属性 响应式引用

// 使用 `toRefs` 创建对 props 的 `user` property 的响应式引用
const { user } = toRefs(props)

in short 把原先的响应式, 改成 Vue.ref 的引用

对原生 dom 引用

<template><div ref="mapContainer" class="map-container"></div>
</template><script setup>
import { ref, onMounted, } from "vue";
var mapContainer = ref(null)
//在这里 mapContainer.value 是 null 的
console.log(mapContainer);
onMounted(()=>{//在dom已挂载的生命周期中, mapContainer.value 才是原生dom的应用console.log(mapContainer);//mapContainer.value
})
</script>

ref 和 reactive

reactiveref 都是用来定义响应式数据
reactive 更推荐去定义复杂的数据类型, ref 更推荐定义基本类型

let app  = Vue.createApp({setup(props, context ) {const data = Vue.ref();data.value = {"name":"李小龙","age":56,}const datarea = Vue.reactive({"age2":564})const addAge = ()=>{data.value.age++;datarea.age2++;}return {data, datarea, addAge} },

在访问上 reactive 不需要.value datarea.age2++;, 而 ref 需要 data.value.age++

简而言之, 一把梭, 全部用ref!

生命周期钩子

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref } from 'vue'
import { onMounted } from 'vue'export default {// in our componentsetup (props) {const repositories = ref([])const getUserRepositories = async () => {repositories.value = await fetchUserRepositories(props.user)}onMounted(getUserRepositories) // on `mounted` call `getUserRepositories`return {repositories,getUserRepositories}//// 这里返回的任何内容都可以用于组件的其余部分}// 组件的"其余部分" data 等
}

in short 把原先的生命周期构造, 改成 setup 内部 Vue.onMounted..等函数回调

生命周期选项

  • beforeCreate
  • created
  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • beforeUnmount
  • unmounted
  • errorCaptured
  • renderTracked
  • renderTriggered
  • activated
  • deactivated
  • serverPrefetch

defineEmits 事件

<script setup>
const emit = defineEmits(['inFocus', 'submit'])function buttonClick() {emit('submit')
}
</script>

defineEmits() 宏不能在子函数中使用。如上所示,它必须直接放置在 <script setup> 的顶级作用域下。

无语法糖版本:

export default {emits: ['inFocus', 'submit'],setup(props, ctx) {ctx.emit('submit')}
}

defineProps 属性Props

类似事件

<script setup>
const props = defineProps(['foo'])console.log(props.foo)
</script>
export default {props: ['foo'],setup(props) {// setup() 接收 props 作为第一个参数console.log(props.foo)}
}

透传属性

透传 Attributes

defineExpose

使用 <script setup> 的组件是默认关闭的——即通过模板引用或者 $parent 链获取到的组件的公开实例,不会暴露任何在 <script setup> 中声明的绑定。

可以通过 defineExpose 编译器宏来显式指定在 <script setup> 组件中要暴露出去的属性:

<script setup>
import { ref } from 'vue'const a = 1
const b = ref(2)defineExpose({a,b
})
</script>

defineOptions这个宏可以用来直接在 <script setup> 中声明组件选项,而不必使用单独的 <script> 块:

<script setup>
defineOptions({inheritAttrs: false,customOptions: {/* ... */}
})
</script>
  • 仅支持 Vue 3.3+。
  • 这是一个宏定义,选项将会被提升到模块作用域中,无法访问 <script setup> 中不是字面常数的局部变量。

watch 响应式

就像我们如何使用 watch 选项在组件内的 user property 上设置侦听器一样, 我们也可以使用从 Vue 导入的 watch 函数执行相同的操作; 它接受 3 个参数:

  1. 一个响应式引用或我们想要侦听的 getter 函数
  2. 一个回调
  3. 可选的配置选项
import { ref, watch } from 'vue'const counter = ref(0)
watch(counter, (newValue, oldValue) => {console.log('The new counter value is: ' + counter.value)
})//监听多个属性
const firstName = ref('')
const lastName = ref('')watch([firstName, lastName], (newValues, prevValues) => {console.log(newValues, prevValues)
})

watchEffect

watch: 没有办法监听一个大对象的属性修改, watchEffect 是个很好的解决办法

立即执行传入的一个函数, 同时响应式追踪其依赖, 并在其依赖变更时重新运行该函数;

watchEffect( ()=> {let pkt = (config.moduleConfig.packagePrefix +"."+ config.moduleConfig.moduleName)console.log("  watch linster (  "+pkt)})

computed 属性

import { ref, computed } from 'vue'const counter = ref(0)
const twiceTheCounter = computed(() => counter.value * 2)counter.value++
console.log(counter.value) // 1
console.log(twiceTheCounter.value) // 2

在这里, computed 函数返回一个作为 computed 的第一个参数传递的 getter 类回调的输出的一个只读的响应式引用; 为了访问新创建的计算变量的 value, 我们需要像使用 ref 一样使用 .value property;

in short 把原先的组件选项, 全部单独拆开,在setup组合起来, 分离逻辑函数,使其可以组合到任意组件!

如何获取this?

getcurrentinstance

getCurrentInstance 支持访问内部组件实例;

Vue.getCurrentInstance().ctx.$refs

getCurrentInstance 只暴露给高阶使用场景, 典型的比如在库中; 强烈反对在应用的代码中使用 getCurrentInstance; 请不要把它当作在组合式 API 中获取 this 的替代方案来使用;

getCurrentInstance 只能在 setup 或生命周期钩子中调用;

大部分情况下, 需要用到 this 的时候, 就要考虑是不是代码有问题了

生命周期

官图
组合式 API:生命周期钩子

image

Provide / Inject 多级组件传参

provide 和 inject 通常成对一起使用,使一个祖先组件作为其后代组件的依赖注入方,无论这个组件的层级有多深都可以注入成功,只要他们处于同一条组件链上。

provide-inject

父组件 Provide

<script>
import { provide } from 'vue'
export default {setup() {const geolocation = reactive({//响应式包装下longitude: 90,latitude: 135})// 建议尽可能将 provide 属性的所有修改限制在定义 provide 的组件内部// <!> 谁分配谁管理const updateLocation = function(val) {this.geolocation.longitude =val}provide('updateLocation', updateLocation)provide('geolocation', geolocation)}
}
</script>

这个 provide 选项应当是一个对象或是返回一个对象的函数。这个对象包含了可注入其后代组件的属性。你可以在这个对象中使用 Symbol 类型的值作为 key

子组件 Inject

<script>
import { inject } from 'vue'export default {setup() {const userGeolocation = inject('geolocation')userGeolocation.longitude //return {userGeolocation}}
}
</script>

和 Props 比较

允许一个祖先组件向其所有子孙后代注入一个依赖, 不论组件层次有多深, 并在起上下游关系成立的时间里始终生效;

官文说的比较透彻

如果你熟悉 React, 这与 React 的 context 特性很相似;

组合式函数

in short 将代码业务逻辑组合起来, 可以随意应用到某个组件上, 类似v2的混入

官文

鼠标跟踪器示例(把鼠标跟踪逻辑抽出来)

// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'// 按照惯例,组合式函数名以“use”开头
export function useMouse() {// 被组合式函数封装和管理的状态const x = ref(0)const y = ref(0)// 组合式函数可以随时更改其状态。function update(event) {x.value = event.pageXy.value = event.pageY}// 一个组合式函数也可以挂靠在所属组件的生命周期上// 来启动和卸载副作用onMounted(() => window.addEventListener('mousemove', update))onUnmounted(() => window.removeEventListener('mousemove', update))// 通过返回值暴露所管理的状态return { x, y }
}

使用

<script setup>
import { useMouse } from './mouse.js'//使用暴露出来的状态
const { x, y } = useMouse()
</script><template>Mouse position is at: {{ x }}, {{ y }}</template>

状态管理(Pinia)

in short 多个组件共享一个共同的数据, 等同与 V2的Vuex

官文

Store (如 Pinia) 是一个保存状态和业务逻辑的实体,它并不与你的组件树绑定。
换句话说,它承载着全局状态。它有点像一个永远存在的组件,每个组件都可以读取和写入它
它有三个概念,state、getter 和 action,我们可以假设这些概念相当于组件中的 data、 computed 和 methods。

更优雅的的 要看看Pinia
Pinia 就是一个实现了上述需求的状态管理库,由 Vue 核心团队维护,对 Vue 2 和 Vue 3 都可用。

实例

https://pinia.vuejs.org/zh/getting-started.html

yarn add pinia
# 或者使用 npm
npm install pinia
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'const pinia = createPinia()
const app = createApp(App)app.use(pinia)
app.mount('#app')

Store (如 Pinia) 是一个保存状态和业务逻辑的实体,它并不与你的组件树绑定。换句话说,它承载着全局状态。它有点像一个永远存在的组件,每个组件都可以读取和写入它。它有三个概念,state、getter 和 action,我们可以假设这些概念相当于组件中的 data、 computed 和 methods。

你可以认为 state 是 store 的数据 (data),getters 是 store 的计算属性 (computed),而 actions 则是方法 (methods)。

State 定义

在大多数情况下,state 都是你的 store 的核心。人们通常会先定义能代表他们 APP 的 state。在 Pinia 中,state 被定义为一个返回初始状态的函数。这使得 Pinia 可以同时支持服务端和客户端。

import { defineStore } from 'pinia'const useStore = defineStore('storeId', {// 为了完整类型推理,推荐使用箭头函数state: () => {return {// 所有这些属性都将自动推断出它们的类型count: 0,name: 'Eduardo',isAdmin: true,items: [],hasChanged: true,}},
})

访问 state
默认情况下,你可以通过 store 实例访问 state,直接对其进行读写。

const store = useStore()
store.count++

Getter 计算属性

Getter 完全等同于 store 的 state 的计算值。可以通过 defineStore() 中的 getters 属性来定义它们。推荐使用箭头函数,并且它将接收 state 作为第一个参数:

export const useStore = defineStore('main', {state: () => ({count: 0,}),getters: {doubleCount: (state) => state.count * 2,},
})

计算属性默认是只读的。当你尝试修改一个计算属性时,你会收到一个运行时警告。只在某些特殊场景中你可能才需要用到“可写”的属性,你可以通过同时提供 getter 和 setter 来创建:

Action 修改

Action 相当于组件中的 method。它们可以通过 defineStore() 中的 actions 属性来定义,并且它们也是定义业务逻辑的完美选择。

export const useCounterStore = defineStore('main', {state: () => ({count: 0,}),actions: {increment() {this.count++},randomizeCounter() {this.count = Math.round(100 * Math.random())},},
})

引入悬浮调试工具

npm install vite-plugin-vue-devtools -D
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import VueDevTools from 'vite-plugin-vue-devtools'export default defineConfig({plugins: [vue(),VueDevTools({launchEditor: 'code' // 指定编辑器,支持 'code', 'webstorm', 'sublime' 等}),],
})

非 STF 项目,使用组合式API

如何在普通html 使用3.0 的组合api? 正解是:Vue.createApp (3.0 API)

每个 Vue 应用都是通过用 createApp 函数创建一个新的应用实例开始的:

const app = Vue.createApp({ /* 选项 */ })

调用 createApp 返回一个应用实例; 该实例提供了一个应用上下文; 应用实例挂载的整个组件树共享相同的上下文, 该上下文提供了之前在 Vue 2.x 中"全局"的配置;

另外, 由于 createApp 方法返回应用实例本身, 因此可以在其后链式调用其它方法, 这些方法可以在以下部分中找到

应用 API

element-plus + vue3

element-plus 安装
element-plus github

vue3 - 应用 & 组件实例

<!DOCTYPE HTML>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1" />
<style type="text/css">
.html{margin: 0;padding: 0;
}
</style><!-- <script src="./js/axios/axios.min.js"></script>
<script src="./js/vue/vue@3.2.26.js"></script> -->
<!-- Import Vue 3 -->
<!-- <script src="//unpkg.com/vue@3"></script> --><!-- Import style --><link rel="stylesheet" href="//unpkg.com/element-plus/dist/index.css" /><!-- Import component library --><script src="//unpkg.com/element-plus"></script><title>my-show</title>
</head><body><div id="app">{{refUpload}}<el-button @click="clickEvent">{{ data.message }}</el-button></div><script>const combOption = Vue.defineComponent({setup: function(props, context ) {const refUpload = Vue.ref(0) //响应式 但访问时要用 refUpload.valueconst data = Vue.reactive({//响应式message: "Hello Element Plus",})const clickEvent = ()=>{//methodsElementPlus.ElMessage.success({message: 'hi'+data.message,type: 'success'});}// const imageUrl = Vue.computed(()=>{}) 计算属性// Vue.nextTick(()=>{}) //nextTick 函数Vue.onMounted(() => { //生命周期函数let inst = Vue.getCurrentInstance()console.log("inst",inst) // inst.ctx 约等于 vue2的实例})return {data, refUpload, clickEvent}}})const app = Vue.createApp(combOption);app.use(ElementPlus);app.mount("#app");</script></body>
</html>

归档

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><script src="/lib/vue@3.2.26.js"></script><script src="/lib/axios.min.js"></script><link rel="stylesheet" href="/lib/element-plus@2.4.0_dist_index.css" /><script src="/lib/element-plus@2.4.0_dist_index.full.js"></script><title>框地</title><style type="text/css">#map-container {/*地图(容器)显示大小*/width: 1366px;height: 800px;}</style>
</head><body><div id="app" style="display: flex;"><div id="map-container"></div><div style="width:200px;">{{refUpload}}<el-button @click="clickEvent">{{ data.message }}</el-button></div></div><script>window._AMapSecurityConfig = {securityJsCode:'d00957714b68cff7f4b9d935f02c02e5',}</script><script>const combOption = Vue.defineComponent({setup: function(props, context ) {const refUpload = Vue.ref(0) const data = Vue.reactive({//响应式message: "Hello Element Plus",})const clickEvent = ()=>{//methodsElementPlus.ElMessage.success({message: 'hi'+data.message,type: 'success'});}// const imageUrl = Vue.computed(()=>{}) 计算属性// Vue.nextTick(()=>{}) //nextTick 函数Vue.onMounted(() => { //生命周期函数let inst = Vue.getCurrentInstance()console.log("inst",inst) // inst.ctx 约等于 vue2的实例})return {data, refUpload, clickEvent}}})const app = Vue.createApp(combOption);app.use(ElementPlus);app.mount("#app");</script>
</body></html>

vite + vue3 + element plus + vue router

https://cn.vuejs.org/guide/quick-start.html

安装依赖包

# npm 6.x
npm create vite@latest MYPROJECt --template vue# npm 7+, extra double-dash is needed:
npm create vite@latest MYPROJECt -- --template vue# 它是 Vue 官方的项目脚手架工具。你将会看到一些诸如 TypeScript 和测试支持之类的可选功能提示:
# npm create vue@latest$ cd MYPROJECt$ npm install vue-router -S
$ npm install element-plus -S
$ npm install axios -S# npm i vue-router@4  element-plus axios -S
# $ npm install -D sass$ npm run dev
//router.js
import * as VueRouter from "vue-router";
const routes = [{path: '/',name: 'index',// redirect: { name: 'index' },component: () => import('.././views/index/Index.vue')}
]
// 3. Create the router instance and pass the `routes` option
// You can pass in additional options here, but let's
// keep it simple for now.
const router = VueRouter.createRouter({// 4. Provide the history implementation to use. We are using the hash history for simplicity here.history: VueRouter.createWebHashHistory(),routes, // short for `routes: routes`})
export default router//main.js
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import router from './router'const app = createApp(App)
app.use(ElementPlus)
app.use(router)
app.mount('#app')

项目配置

config

删除缓存

修改node_modules库的源码后, 总是不更新..

你想要强制 Vite 重新构建依赖,你可以用 --force 命令行选项启动开发服务器,或者手动删除 node_modules/.vite 目录。
https://cn.vitejs.dev/guide/dep-pre-bundling.html#caching

配置 import '@/xxx' 别名

  1. 首先安装 path
    npm install --save-dev @types/node

  2. 配置 vite.config.js
    vite.config.js

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from "path";
// https://vitejs.dev/config/
export default defineConfig({base: "./",plugins: [vue()],resolve: {alias: {'@': path.resolve(__dirname, 'src'),    //解析@'assets': path.resolve(__dirname, 'src/assets')    //解析assets},},
})

https://cn.vitejs.dev/config/shared-options.html#resolve-alias

配置打包相对路径

  1. vite.config.js配置
export default defineConfig({// base: "/bar/"base: "./",
})
  1. router 配置
const router = createRouter({// 需要使用 hash模式 // history: createWebHashHistory("/bar/"),history: createWebHashHistory("./"),// 路由地址routes
});

配置环境变量

.env.production 注意, 变量名必须是 VITE_APP 开头

# 高德地图
VITE_APP__AMAP_WEB_JS_KEY=d8855d6xxxx03xxx082d9
VITE_APP__AMAP_SECURITY_JS_CODE=dfb9cxxx03c2xxx054xxx0xxx00a77
# 天地图
VITE_APP__MAP_WEB_JS_KEY=4dxxx0xxx0x0ac9fc2543f
# 接口地址
VITE_APP__BASE_URL='http://127.0.0.1:8010/xxx-api'

vite.config.ts

export default defineConfig(({ command, mode }: ConfigEnv): UserConfig => {
  const env = loadEnv(mode, process.cwd());//这一行
  console.log("启动为",command, mode);
  return {
   ....
   

使用

const AMAP_WEB_JS_KEY = import.meta.env.VITE_APP__AMAP_WEB_JS_KEY;
const AMAP_SECURITY_JS_CODE = import.meta.env.VITE_APP__AMAP_SECURITY_JS_CODE;
//const AMAP_SECURITY_JS_CODE = process.env.VITE_APP__AMAP_SECURITY_JS_CODE;

Vite

官网指南

在浏览器支持 ES 模块之前,JavaScript 并没有提供原生机制让开发者以模块化的方式进行开发。这也正是我们对 “打包” 这个概念熟悉的原因:使用工具抓取、处理并将我们的源码模块串联成可以在浏览器中运行的文件。

时过境迁,我们见证了诸如 webpack、Rollup 和 Parcel 等工具的变迁,它们极大地改善了前端开发者的开发体验。

然而,当我们开始构建越来越大型的应用时,需要处理的 JavaScript 代码量也呈指数级增长。包含数千个模块的大型项目相当普遍。基于 JavaScript 开发的工具就会开始遇到性能瓶颈:通常需要很长时间(甚至是几分钟!)才能启动开发服务器,即使使用模块热替换(HMR),文件修改后的效果也需要几秒钟才能在浏览器中反映出来。如此循环往复,迟钝的反馈会极大地影响开发者的开发效率和幸福感。

Vite(法语意为 "快速的",发音 /vit/,发音同 "veet")是一种新型前端构建工具,能够显著提升前端开发体验。它主要由两部分组成:

  1. 一个开发服务器,它基于 原生 ES 模块 提供了 丰富的内建功能,如速度快到惊人的 模块热更新(HMR)。
  2. 一套构建指令,它使用 Rollup 打包你的代码,并且它是预配置的,可输出用于生产环境的高度优化过的静态资源。

命令行参数

命令行参数

vite --host

--host [host] 指定主机名称 (string)
--port <port> 指定端口 (number)

env 文件

[env 文件](https://cn.vitejs.dev/guide/env-and-mode.html#env-files

Vite 使用 dotenv 从你的 环境目录 中的下列文件加载额外的环境变量:

.env                # 所有情况下都会加载
.env.local          # 所有情况下都会加载,但会被 git 忽略
.env.[mode]         # 只在指定模式下加载
.env.[mode].local   # 只在指定模式下加载,但会被 git 忽略

默认情况下,开发服务器 (dev 命令) 运行在 development (开发) 模式,而 build 命令则运行在 production (生产) 模式。

即默认:
.env 通用
.env.development 仅开发
.env.production 仅生产

为了防止意外地将一些环境变量泄漏到客户端,只有以 VITE_ 为前缀的变量才会暴露给经过 vite 处理的代码。

.env 类文件会在 Vite 启动一开始时被加载,而改动会在重启服务器后生效。

使用插件

Vite 可以使用插件进行扩展,这得益于 Rollup 优秀的插件接口设计和一部分 Vite 独有的额外选项。这意味着 Vite 用户可以利用 Rollup 插件的强大生态系统,同时根据需要也能够扩展开发服务器和 SSR 功能。

若要使用一个插件,需要将它添加到项目的 devDependencies 并在 vite.config.js 配置文件中的 plugins 数组中引入它。例如,要想为传统浏览器提供支持,可以按下面这样使用官方插件 @vitejs/plugin-legacy:

$ npm add -D @vitejs/plugin-legacy

// vite.config.js
import legacy from '@vitejs/plugin-legacy'
import { defineConfig } from 'vite'export default defineConfig({plugins: [legacy({targets: ['defaults', 'not IE 11'],}),],
})

打包二级路径

  1. vite.config.js配置
export default defineConfig({// base: "/bar/"base: "./",
})
  1. router 配置
const router = createRouter({// 需要使用 hash模式 // history: createWebHashHistory("/bar/"),history: createWebHashHistory("./"),// 路由地址routes
});

import 查找路径

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'export default defineConfig({plugins: [vue()],resolve: {alias:{'@':  path.resolve(__dirname, 'src') }}
})

Vue Router

API 参考

npm install vue-router

组合式API

with vue 3

// vue-router中提供3种的路由模式
import { createWebHistory, createRouter } from 'vue-router'import HelloWorld from '@/components/HelloWorld.vue'
const routes  = [{path: '/',component: HelloWorld,children: []}]
const router = createRouter({// 路由的模式history: createWebHistory(),// 路由规则routes})export default router ;

main.js

import router from './router'import { createApp } from 'vue'
import App from './App.vue'const app = createApp(App)//实例
app.use(router)//app.mount('#app')

在 setup 中访问

因为我们在 setup 里面没有访问 this,所以我们不能再直接访问 this.$routerthis.$route。作为替代,我们使用 useRouteruseRoute 函数:

import { useRouter, useRoute } from 'vue-router'export default {setup() {const router = useRouter()const route = useRoute()function pushWithQuery(query) {router.push({name: 'search',query: {...route.query,},})}},
}

导航守卫(拦截)

import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'export default {setup() {// 与 beforeRouteLeave 相同,无法访问 `this`onBeforeRouteLeave((to, from) => {const answer = window.confirm('Do you really want to leave? you have unsaved changes!')// 取消导航并停留在同一页面上if (!answer) return false})const userData = ref()// 与 beforeRouteUpdate 相同,无法访问 `this`onBeforeRouteUpdate(async (to, from) => {//仅当 id 更改时才获取用户,例如仅 query 或 hash 值已更改if (to.params.id !== from.params.id) {userData.value = await fetchUser(to.params.id)}})},
}

组合式 API 守卫也可以用在任何由 <router-view> 渲染的组件中,它们不必像组件内守卫那样直接用在路由组件上。

路由传参

将 props 传递给路由组件

const User = {// 请确保添加一个与路由参数完全相同的 prop 名props: ['id'],template: '<div>User {{ id }}</div>'
}
const routes = [{ path: '/user/:id', component: User, props: true }]

当 props 是一个对象时,它将原样设置为组件 props。当 props 是静态的时候很有用。

const routes = [{path: '/promotion/from-newsletter',component: Promotion,props: { newsletterPopup: false }}
]

动态路由一则

1. 路由钩子


//路由数据
import routes from './routes'
//vuex
import store from '@/store/index'//路由实例
const router = new VueRouter({routes
})///// 在路由钩子中判断处理代码
router.beforeEach(async (to, from, next) => {//是否存有token作为验证是否登录的条件const token = util.cookies.get('token')const isLogin = (token && token !== 'undefined')// 加载路由, 菜单;if(isLogin && !store.state.d2admin.user.MenuLoad){await store.dispatch('d2admin/user/confirmLoadDynamicMenu', router)if(from.name === 'login'){next(to.query.redirect || '/')}else{next(to.fullPath)}return;}if(isLogin && to.name === 'login'){next(to.query.redirect || '/')return ;}
}

2. vuex 判断加载

vuex 中user 的 actions

export default {namespaced: true,state: {// 用户信息info: {},DynamicMenu: false},actions: {async confirmLoadDynamicMenu ({ state, commit }, $router) {if (state.DynamicMenu) {//已加载}else{//未加载, 请求接口, 获取到动态菜单组件...注册到路由var comp = `oa/Index`;$router.addRoute({ path: '/demo/components/oa/index', component: ()=>import("@/views/"+comp) });//设置 vuex 数据var warpPath = covDatacommit('page/init', covData,{root: true})// vuex?commit('SetMenuLoad', true)}},}.......

官文-添加路由

动态路由主要通过两个函数实现。router.addRoute() 和 router.removeRoute()。
它们只注册一个新的路由,也就是说,如果新增加的路由与当前位置相匹配,就需要你用 router.push() 或 router.replace() 来手动导航,才能显示该新路由。

如果你决定在导航守卫内部添加或删除路由,你不应该调用 router.replace(),而是通过返回新的位置来触发重定向:

router.beforeEach(to => {if (!hasNecessaryRoute(to)) {router.addRoute(generateRoute(to))// 触发重定向return to.fullPath}
})

第一,新添加的路由记录将与 to 位置相匹配,实际上导致与我们试图访问的位置不同。
第二,hasNecessaryRoute() 在添加新的路由后返回 false,以避免无限重定向。

3. 添加嵌套路由

router.addRoute({ name: 'admin', path: '/admin', component: Admin })
router.addRoute('admin', { path: 'settings', component: AdminSettings })//等效于:
router.addRoute({name: 'admin',path: '/admin',component: Admin,children: [{ path: 'settings', component: AdminSettings }],
})