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-组件选项

新的 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:生命周期钩子

lifecycle

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())
    },
  },
})

非 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=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”)是一种新型前端构建工具,能够显著提升前端开发体验。它主要由两部分组成:

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

组件模版 ----------------------------

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