2021-?-?
Vue 3
V3 官页 V3 官页文档 V3 官页API文档 文档 - 应用 API 文档 - 全局 API
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 的修饰符 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 组件选项在创建组件之前执行, 一旦 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) // 0
counter.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
reactive 和 ref 都是用来定义响应式数据
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 component
setup (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)
}
}透传属性
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 个参数:
- 一个响应式引用或我们想要侦听的 getter 函数
- 一个回调
- 可选的配置选项
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 支持访问内部组件实例;
Vue.getCurrentInstance().ctx.$refs
getCurrentInstance 只暴露给高阶使用场景, 典型的比如在库中; 强烈反对在应用的代码中使用 getCurrentInstance; 请不要把它当作在组合式 API 中获取 this 的替代方案来使用;
getCurrentInstance 只能在 setup 或生命周期钩子中调用;
大部分情况下, 需要用到 this 的时候, 就要考虑是不是代码有问题了
生命周期

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.pageX
y.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 piniaimport { 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())
},
},
})非 STF 项目,使用组合式API
如何在普通html 使用3.0 的组合api? 正解是:Vue.createApp (3.0 API)
每个 Vue 应用都是通过用 createApp 函数创建一个新的应用实例开始的:
const app = Vue.createApp({ /* 选项 */ })
调用 createApp 返回一个应用实例; 该实例提供了一个应用上下文; 应用实例挂载的整个组件树共享相同的上下文, 该上下文提供了之前在 Vue 2.x 中”全局”的配置;
另外, 由于 createApp 方法返回应用实例本身, 因此可以在其后链式调用其它方法, 这些方法可以在以下部分中找到
element-plus + vue3
element-plus 安装 element-plus github
<!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.value
const data = Vue.reactive({//响应式
message: "Hello Element Plus",
})
const clickEvent = ()=>{//methods
ElementPlus.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 = ()=>{//methods
ElementPlus.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')项目配置
删除缓存
修改node_modules库的源码后, 总是不更新..
你想要强制 Vite 重新构建依赖,你可以用 --force 命令行选项启动开发服务器,或者手动删除 node_modules/.vite 目录。
https://cn.vitejs.dev/guide/dep-pre-bundling.html#caching
配置 import ’@/xxx’ 别名
-
首先安装 path
npm install --save-dev @types/node -
配置 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
配置打包相对路径
vite.config.js配置
export default defineConfig({
// base: "/bar/"
base: "./",
})- router 配置
const router = createRouter({
// 需要使用 hash模式
// history: createWebHashHistory("/bar/"),
history: createWebHashHistory("./"),
// 路由地址
routes
});
配置环境变量
.env.production 注意, 变量名必须是 VITE_APP 开头
# 高德地图
VITE_APP__AMAP_WEB_JS_KEY=d8855d60371773a465538851659c82d9
VITE_APP__AMAP_SECURITY_JS_CODE=dfb9cdb413c254a3bdf54aacefaf0a77
# 天地图
VITE_APP__MAP_WEB_JS_KEY=4d82ed9982503737c40d72ac9fc2543f
# 接口地址
VITE_APP__BASE_URL='http://127.0.0.1:8010/nyfwzxjs-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”)是一种新型前端构建工具,能够显著提升前端开发体验。它主要由两部分组成:
- 一个开发服务器,它基于 原生 ES 模块 提供了 丰富的内建功能,如速度快到惊人的 模块热更新(HMR)。
- 一套构建指令,它使用 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'],
}),
],
})打包二级路径
vite.config.js配置
export default defineConfig({
// base: "/bar/"
base: "./",
})- 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
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.$router 或 this.$route。作为替代,我们使用 useRouter 和 useRoute 函数:
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 = covData
commit('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 }],
})组件模版 ----------------------------
<template>
<div class="container">
<h1>{{msg}}</h1>
</div>
</template>
<script>
import { ref, computed } from 'vue'
import { watch, watchEffect } from 'vue'
import { onMounted, onUnmounted } from 'vue'
import { defineEmits, defineProps } from 'vue'
import { provide , inject} from 'vue'
export default {
setup(props, context) {
const msg = ref("xxxxxx hello")
return {msg}
}
}
</script>
<style lang="scss" scoped>
.container{
width: 100%;
height: 100%;
}
</style><template>
<div class="container">
<h1>{{msg}}</h1>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { watch, watchEffect } from 'vue'
import { onMounted, onUnmounted } from 'vue'
import { defineEmits, defineProps } from 'vue'
import { provide , inject} from 'vue'
const msg = ref("xxxxxx hello")
</script>
<style lang="scss" scoped>
.container{
width: 100%;
height: 100%;
}
</style>