惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Apple Machine Learning Research
Apple Machine Learning Research
J
Java Code Geeks
博客园 - 聂微东
Microsoft Azure Blog
Microsoft Azure Blog
量子位
T
Tailwind CSS Blog
Vercel News
Vercel News
I
InfoQ
Stack Overflow Blog
Stack Overflow Blog
U
Unit 42
Engineering at Meta
Engineering at Meta
L
LangChain Blog
大猫的无限游戏
大猫的无限游戏
D
Docker
博客园_首页
P
Proofpoint News Feed
月光博客
月光博客
T
The Blog of Author Tim Ferriss
MyScale Blog
MyScale Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
Martin Fowler
Martin Fowler
腾讯CDC
N
Netflix TechBlog - Medium
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

博客园 - daidaidaiyu

一文入门 Apache Cassandra 一文学习 Android 原生开发 ZooKeepper 一文入门 ThingsBoard 规则链系统源码分析和自定义定时器 嵌入式开发入门知识 一文入门和实践Kafka 一文学习入门 ThingsBoard 开源物联网平台 Claude Code 使用一则 JMS, ActiveMQ 学习一则 Nacos实例一则及其源码环境搭建 一文学习 工作流开发 BPMN、 Flowable 一文学习 Spring 声明式事务源码全流程总结 Spring IOC 源码学习 事务增强相关的对象创建 Spring IOC 源码学习 事务相关的 BeanDefinition 解析过程 (XML) GIS 核心知识 Spring IOC 源码学习 声明式事务的入口点 23.行为型 - 访问者模式 (Visitor Pattern) 一文学习 Spring AOP 源码全过程 22.行为型 - 迭代器模式 (Iterator Pattern) 21.行为型 - 状态模式 (State Pattern) Netty 核心知识 SpringCloud 微服务实现一则 (Eureka + Hystrix) 20.行为型 - 责任链模式(ChainofResponsibility Pattern) Modbus 协议 学习一则 - daidaidaiyu 19.行为型 - 策略模式(Strategy Pattern) - daidaidaiyu Redis 学习一则 - daidaidaiyu Android NDK 开发一则 - daidaidaiyu
Vue2 速升V3手册
daidaidaiyu · 2026-08-11 · via 博客园 - daidaidaiyu

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) // 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

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 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..等函数回调

生命周期选项

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.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 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.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')

项目配置

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 = 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 }],
})