Vue3
基础内容
创建项目
使用 Vue-cli 创建(已安装 18.3 或更高版本的 Node.js)
官方更推荐使用 vite 创建 vue3 项目
jsvue -V pnpm create vue@latest // *创建vue3项目 pnpm dev //运行项目使用 Vite 创建
jsnpm create vite@latest // 使用npm创建项目(vue、react、) yarn create vite // 使用yarn创建项目 pnpm create vite // 使用pnpm创建项目 npm init vite-app 项目名 // *创建vue3项目 npm install //安装依赖! npm run dev //运行项目
关闭语法检查
- 在 项目根目录下 vue.config.js 中进行配置
module.exports = {
lintOnSave: false, //关闭语法检查 修改配置后需要重启
};main.js
- 入口文件 发生改动(不兼容 Vue2 的写法)
// 引入createApp工厂函数 创建vm
import { createApp } from "vue";
import App from "./App.vue";
createApp(App).mount("#app"); // app为index.html容器的id
/*
createApp(App).mount('#app') 可拆分为
const app=createApp(App) // 相当于创建vm,但app比vm更轻
app.mount('#app') // 挂载到节点
*/
/* 引入的不再是Vue构造函数
new Vue({
render: h => h(App),
}).$mount('#app')
*/全局 API 的转移
Vue 2.x 有许多全局 API 和配置。
例如:注册全局组件、注册全局指令等。
js//注册全局组件 Vue.component('MyButton', { data: () => ({ count: 0 }), template: '<button @click="count++">Clicked {{ count }} times.</button>' }) //注册全局指令 Vue.directive('focus', { inserted: el => el.focus() }
Vue3.0 中对这些 API 做出了调整:
将全局的 API,即:
Vue.xxx调整到应用实例(app)上2.x 全局 API( Vue)3.x 实例 API ( app)Vue.config.xxxx app.config.xxxx Vue.config.productionTip 移除 Vue.component app.component Vue.directive app.directive Vue.mixin app.mixin Vue.use app.use Vue.prototype app.config.globalProperties
其他改变
data 选项应始终被声明为一个函数。
过度类名的更改:
Vue2.x 写法
css.v-enter, .v-leave-to { opacity: 0; } .v-leave, .v-enter-to { opacity: 1; }Vue3.x 写法
css.v-enter-from, .v-leave-to { opacity: 0; } .v-leave-from, .v-enter-to { opacity: 1; }
父组件中绑定事件
vue<my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" />子组件中声明自定义事件
vue<script> export default { emits: ["close"], }; </script>移除keyCode 作为 v-on 的修饰符,同时也不再支持
config.keyCodes移除
v-on.native修饰符移除过滤器(filter)
过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。
......
预处理器
代码块可以使用
lang这个 attribute 来声明预处理器语言,最常见的用例就是在<script>中使用 TypeScript:lang在任意块上都能使用,比如我们可以在<style>标签中使用 SASS 或是<template>中使用 Pug:vue<script lang="ts"> // use TypeScript </script> <template lang="pug"> p {{ msg }} </template> <style lang="scss"> $primary-color: #333; body { color: $primary-color; } </style>
杂项
Vue 文件结构中可以 存在多个标签,不再要求只有一个根标签包裹
Vue3 中使用 Vue2 的 data、methods 配置依旧生效,尽量不要与 Vue2.x 配置混用
- Vue2.x 配置(data、methos、computed...)中可以访问到setup 中的属性、方法。
- 但在 setup 中不能访问到Vue2.x 配置(data、methos、computed...)。
- 如果有重名, setup 优先。
Vue3 中 setup 配置的响应式数据,是深层次的
- 可以对代理过的数据 直接进行删除、添加操作,同时界面跟随变化
单向数据流 props
组件间数据传递时,依旧需要使用 props 进行接收
vue2 中接收后可以直接使用
vue3 中接收后可通过setup 函数的第一个参数拿到,也能直接使用
- js
export default { props: { text: { type: String, default: "", }, message: Number, }, setup(props: Data) { const { text } = toRefs(props); const formatText = `Hi,${text.value}`; return { formatText, }; }, }; 使用
setup语法糖时,需要用defineProps编译器宏无需引入、定义接收propsjsconst props = defineProps({ text: String, message: Number, }); // 或 const props = defineProps(["text", "message"]);
具名插槽
- 为组件命名时,推荐使用
v-slot:xxx避免使用slot="xxx"
- 为组件命名时,推荐使用
组合式 API(常用)
setup 配置
- Vue3 中的一个配置项,值为一个函数;是所有 Composition API(组合式 api)的
表演舞台- 组件中所有用到的:数据、方法、计算属性等,均要配置在 setup 中
- setup 的两种返回值
- 若返回一个对象,则对象中的属性、方法,在模板中均可直接使用
- 若返回一个渲染函数:可以自定义渲染内容!
- 需要单独引入 渲染函数 h
import {h} from 'vue' - 返回值是一个函数,函数中需要 再返回 h 函数调用的结果
template模板中的内容会被完全替换为 h 函数调用的结果
- 需要单独引入 渲染函数 h
- 注意点:
- setup**执行时机 **
- 在 beforeCreate 之前执行一次,this 指向 undefined
- setup 接受的参数
- 参数 1 props:通过 props 接收的父组件传递过来的数据(具有响应式)
- 多传未收有警告,未传多收不警告
- 参数 2 context:
- attrs:值为对象,包含组件外部传递过来,但没有在 props 配置中声明的属性, 相当于
this.$attrs - slots: 收到的插槽内容, 相当于
this.$slots - emit: 分发自定义事件的函数, 相当于
this.$emit
- attrs:值为对象,包含组件外部传递过来,但没有在 props 配置中声明的属性, 相当于
- 参数 1 props:通过 props 接收的父组件传递过来的数据(具有响应式)
- setup 不能是一个 async 函数,因为返回值不再是 return 的对象, 而是 promise, 模板看不到 return 对象中的属性。(后期也可以返回一个 Promise 实例,但需要 Suspense 和异步组件的配合)
- setup**执行时机 **
<!-- setup 的第一种返回值,对象中的属性、方法,在模板中均可直接使用 -->
<template>
<h2>姓名{{ name }}</h2>
<h2>年龄{{ age }}</h2>
<button @click="sayHello">说话</button>
</template>
<script>
export default {
name: "App", //配置组件名
setup() {
// 数据
let name = "张三";
let age = 18;
//方法
function sayHello() {
alert(`你好,我是${name}`);
}
return {
name,
age,
sayHello,
};
},
};
</script><!-- setup 的第二种返回值,渲染函数,自定义渲染内容 -->
<template>
<h2>23456 会被替换的内容</h2>
</template>
<script>
import { h } from "vue";
export default {
name: "App", //配置组件名
setup() {
// 数据
let name = "张三";
let age = 18;
//方法
function sayHello() {
alert(`你好,我是${name}`);
}
return () => {
return h("h1", name);
};
},
};
</script>ref 函数
作用: 定义一个响应式的数据
- 引入
import {ref} from 'vue'
- 引入
语法:
const xxx = ref(initValue)- 创建一个包含响应式数据的引用对象(reference 对象,简称 ref 对象)。
- JS 中操作数据需要携带 .value:
xxx.value - 模板中读取数据: 不需要.value,直接使用插值语法
备注:
- 接收的数据可以是:基本类型、也可以是对象类型。
- 基本类型的数据:响应式依然是靠
Object.defineProperty()的get与set完成的。 - 对象类型的数据:内部 “ 求助 ” 了 Vue3.0 中的一个新函数——
reactive函数。(Proxy 实现)
- vue
<template> <h2>姓名{{ name }}</h2> <h2>工作{{ job.type }}</h2> <button @click="changeInfo">修改信息</button> </template> <script> import { ref } from "vue"; export default { name: "App", //配置组件名 setup() { // 数据 let name = ref("张三"); let job = ref({ type: "前端工程师", salary: "30k", }); //方法 function changeInfo() { name.value = "李四"; job.value.type = "UI设计师"; } return { name, job, changeInfo, }; }, }; </script>
模板引用
ref是一个特殊的 attribute,和v-for章节中提到的key类似允许我们在一个特定的 DOM 元素或子组件实例被挂载后,获得对它的直接引用
使用细节
- 如果不使用
<script setup>,需确保从setup()返回 ref 的引用 input - 只可以在组件挂载后才能访问模板引用,初次渲染时元素不存在,值为 null
- 可用 watchEffect 侦听模板引用的变化。
<script setup lang="ts">
import { ref, watchEffect } from "vue";
// 声明一个 ref 来存放该元素的引用
// 必须和模板里的 ref 同名
const input = ref<HTMLInputElement | null>(null);
watchEffect(() => {
if (input.value) {
input.value.focus();
} else {
// 此时还未挂载,或此元素已经被卸载(例如通过 v-if 控制)
}
});
</script>
<template>
<input ref="input" />
</template>v-for 中的模板引用
在
v-for中使用模板引用时,对应的 ref 中包含的值是一个数组,它将在元素被挂载后包含对应整个列表的所有元素
- 注意:ref 数组并不保证与源数组相同的顺序
<script setup>
import { ref, onMounted } from "vue";
const list = ref([1, 2, 3]);
const itemRefs = ref([]);
onMounted(() => console.log(itemRefs.value));
</script>
<template>
<ul>
<li v-for="item in list" ref="itemRefs">
{{ item }}
</li>
</ul>
</template>组件 ref
能够获得组件实例
注意:
- 使用了
<script setup>的组件是默认私有的:一个父组件无法访问到一个使用了<script setup>的子组件中的任何东西,除非子组件在其中通过defineExpose宏显式暴露:
<script setup>
import { ref, onMounted } from "vue";
import Child from "./Child.vue";
const child = ref(null);
const a = ref(null);
onMounted(() => {
// child.value 是 <Child /> 组件的实例
});
// 像 defineExpose 这样的编译器宏不需要导入
defineExpose({
a,
});
</script>
<template>
<Child ref="child" />
</template>函数模板引用
refattribute 还可以绑定为一个函数,会在每次组件更新时都被调用。该函数会收到元素引用作为其第一个参数需要使用动态的
:ref绑定才能够传入一个函数。当绑定的元素被卸载时,函数也会被调用一次,此时的el参数会是null
<input :ref="(el) => { /* 将 el 赋值给一个数据属性或 ref 变量 */ }">reactive 函数
- 作用: 定义一个
对象类型的响应式数据(基本类型,要用ref函数) - 语法:
const 代理对象= reactive(源对象)- 接收一个 对象 / 数组,返回一个代理对象(Proxy 的实例对象,简称 proxy 对象)
- 对于数组可以直接通过下标进行修改
- reactive 定义的响应式数据是“深层次的”。
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。
setup(){
// 数据
let name=ref('张三'),
let job=ref({
type:'前端开发',
salary:{
number:'12k'
}
})
let hobby=reactive(['吸烟','喝酒','打游戏'])
// 方法
function changeInfo(){
name.value='李四',
job.value.salary.number='32k'
hobby[1]='吃饭'
}
}reactive 与 ref
- 从定义数据角度对比:
- ref 用来定义:基本类型数据
- reactive 用来定义:对象(或数组)类型数据。
- 备注:ref 也可以用来定义对象(或数组)类型数据, 它内部会自动通过
reactive转为代理对象。
- 从原理角度对比:
- ref 通过
Object.defineProperty()的get与set来实现响应式(数据劫持)。 - reactive 通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作 源对象 内部的数据。
- ref 通过
- 从使用角度对比:
- ref 定义的数据:操作数据需要
.value,读取数据时模板中直接读取不需要.value。 - reactive 定义的数据:操作数据与读取数据:均不需要
.value。
- ref 定义的数据:操作数据需要
计算与侦听属性
computed 计算属性
使用步骤 与 vue2 中基本一致
vue<script> // 使用前先从 vue 引入 computed (组合式api) import {reactive,computed} from 'vue' export default { name:'Dome', setup(){ // 数据 let person = reactive({ firstName:'张', lastName:'三' }) // 计算属性(简写 不考虑计算属性被修改的情况) person.fullName = computed(()=>{ return person.firstName + '-' + person.lastName }) // 计算属性(完整写法 ) person.fullNames = computed({ get(){ return person.firstName + '-' + person.lastName } set(value){ const nameArr=value.split('-') person.firstName=nameArr[0] person.lastName=nameArr[1] } }) return { person } } } </script>
watch 侦听属性
watch 三个参数
- 参数 1:需要监视的响应式数据,可以是对象
- 参数 2:当侦听数据改变时触发的回调
- 参数 3:配置对象,watch 的其他配置
两个小“坑”:
- 监视 reactive 定义的响应式数据时:oldValue 无法正确获取、强制开启了深度监视(deep 配置失效,不能手动关闭)
- 监视 reactive 定义的响应式数据中某个属性时:deep 配置有效
- ref 监视对象,底层还是 reactive,道理同上
.value问题- ref 定义的基本数据类型 做 watch 时,不需要
.value - ref 定义的对象数据类型 做 watch 时,需要
.value或 开启深度监视
- ref 定义的基本数据类型 做 watch 时,不需要
// 使用前先从 vue 引入 watch (组合式api)
import {ref,reactive,watch} from 'vue'
export default {
name:'Dome',
setuip(){
// 数据
let sum = ref(0)
let msg=ref('你好!')
let person=reactive({
name:'张三',
age:18
job:{
j1:{salary:20}
}
})
// 情况一:监视ref所定义的响应式数据
watch(sum,(newValue,oldValue)=>{
console.log('sum改变了',newValue,oldValue)
},{immediate:true})
// 情况二:监视ref所定义的多个响应式数据
watch([sum,msg],(newValue,oldValue)=>{
console.log('sum或msg变了',newValue,oldValue)
})
/* 情况三:监视reactive定义的响应式数据
若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
若watch监视的是reactive定义的响应式数据,则强制开启了深度监视
*/
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
},{immediate:true,deep:false}) //此处的deep配置不再奏效
//情况四:监视reactive定义的响应式数据中的某个属性
// 参数1不能再直接写 person.job
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true})
//情况五:监视reactive定义的响应式数据中的某些属性
watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true})
//特殊情况 监视job,修改job深层次数据时,需要开启深度监视
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{deep:true}) //此处由于监视的是reactive所定义的对象中的某个属性,所以deep配置有效
return { sum }
}
}watchEffect
watch 的套路是:既要指明监视的属性,也要指明监视的回调。
watchEffect 的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
watchEffect 有点像 computed:
- 但 computed 注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
- 而 watchEffect 更注重的是过程(回调函数的函数体),所以不用写返回值。
js//引入 watchEffect import { ref, reactive, watchEffect } from "vue"; //watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。 watchEffect(() => { const x1 = sum.value; const x2 = person.age; console.log("watchEffect配置的回调执行了"); });
Vue3 生命周期
Vue3.0 中 可以继续使用 Vue2.x 中的生命周期钩子,但有有两个被更名:
beforeDestroy改名为beforeUnmountdestroyed改名为unmounted
Vue3.0 提供
组合式API形式的生命周期钩子:beforeCreate===>setup()created=======>setup()beforeMount===>onBeforeMountmounted=======>onMountedbeforeUpdate===>onBeforeUpdateupdated=======>onUpdatedbeforeUnmount==>onBeforeUnmountunmounted=====>onUnmounted
js//使用前必须先引入 import {onBeforeMount,onMounted,...} from 'vue' setup(){ onBeforeMount(()=>{ console.log('--onBeforeMount') }) onMounted(()=>{ console.log('--onBeforeMount') }) }如果组合式生命周期钩子、配置项形式的生命周期钩子同时存在
- 组合式 api 生命周期先调用,对应配置项钩子后调用
hook 函数
本质是一个函数,把 setup 函数中使用的 Composition API 进行了封装。
类似于 vue2.x 中的 mixin。
自定义 hook 的优势: 复用代码, 让 setup 中的逻辑更清楚易懂。
- src 文件下新建 hooks 文件夹 - 新建 xxx.js 文件
// 把需要复用的内容放在js文件中 函数形式暴露,并return其中的数据
import {reactive,onMounted,onBeforeUnmount} from 'vue'
export default function(){
// 可复用的数据
let point=reactive({ x:0,y:0 })
// 可复用的方法
function xxxx(){ ... }
// 可复用的生命周期钩子
onMounted(){ ... }
onBeforeUnmount(){ ... }
return point
}
// 再其他组件中使用hook函数 引入当前文件配置即可
import xxx from '../hooks/xxx' // 引入hooks下的某个js文件,接收、执行函数并return接收到的值
export default {
name:'Demo',
setup(){
const point = xxx()
return {point}
}
}toRef 与 toRefs
作用:创建一个 ref 对象,其 value 值指向另一个对象中的某个属性,保留响应式
语法:
const name = toRef(person,'name')应用: 要将响应式对象中的某个属性单独提供给外部使用时。
扩展:
toRefs与toRef功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)js// toRef 的第一个参数值是个对象即可,第二个参数是对象中数据的键 // toRefs 直接传入一个对象即可,与toRef功能一致(拆除外面一层,方便模板书写) import {reactive,toRef} from 'vue' export default { name:'Demo', setup(){ let person=reactive({ name:'张三', obj:{age:30} }) const name1=person.name // name1将失去响应式,只是的得到对应字符串 const name2=toRef(person,'name')//借助toRef,指向原数据,保留响应式 return { name:toRef(person,'name'), age:toRef(person.obj,'age'), ...toRefs(person) } } }
组合式 API(其他)
shallowReactive 与 shallowRef
shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。
什么时候使用? 做性能优化
- 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
- 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。
- 保持 x 的响应式,确保替换操作仍能引起页面的变化
js// 组合式api 使用前先引入 import { shallowRef, shallowReactive } from "vue";
readonly 与 shallowReadonly
readonly: 让一个响应式数据变为只读的(深只读)。
shallowReadonly:让一个响应式数据变为只读的(浅只读)。
应用场景: 不希望数据被修改时。
jsimport {ref,reactive,toRefs,readonly} from 'vue' setup(){ let person = reactive({ name:'张三', age:13, job:{ salary:20 } }) // readonly 中传入一个响应式数据,转化为只读的 person = readonly(person) return { sum, ...toRefs(person) } }
toRaw 与 markRaw
- toRaw:
- 作用:将一个由
reactive生成的响应式对象转为普通对象。 - 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
- 作用:将一个由
- markRaw:
markRaw(响应式对象)- 作用:标记一个对象,使其永远不会再成为响应式对象。
- 应用场景:
- 有些值不应被设置为响应式的, 例如复杂的第三方类库等。
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
customRef 自定义 Ref
作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。
实现防抖效果:
vue<template> <input type="text" v-model="keyword" /> <h3>{{ keyword }}</h3> </template> <script> import { ref, customRef } from "vue"; export default { name: "Demo", setup() { // let keyword = ref('hello') //使用Vue准备好的内置ref //自定义一个myRef function myRef(value, delay) { let timer; //通过customRef去实现自定义 return customRef((track, trigger) => { return { get() { track(); //告诉Vue这个value值是需要被“追踪”的 return value; }, set(newValue) { clearTimeout(timer); timer = setTimeout(() => { value = newValue; trigger(); //告诉Vue去更新界面 }, delay); }, }; }); } let keyword = myRef("hello", 500); //使用程序员自定义的ref return { keyword, }; }, }; </script>
provide 与 inject
作用:实现祖与后代组件间通信
套路:父组件有一个
provide选项来提供数据,后代组件有一个inject选项来开始使用这些数据具体写法:
祖组件中:
jssetup(){ ...... let car = reactive({name:'奔驰',price:'40万'}) provide('car',car) ...... }后代组件中:
jssetup(props,context){ ...... const car = inject('car') return {car} ...... }
响应式数据的判断
返回值 true / false
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive创建的响应式代理 - isReadonly: 检查一个对象是否是由
readonly创建的只读代理 - isProxy: 检查一个对象是否是由
reactive或者readonly方法创建的代理
新增组件
Teleport
什么是 Teleport?——
Teleport是一种能够将我们的组件 html 结构移动到指定位置的技术。- to 的值可以是 id
#app标签名body的形式 - 能够将其中包裹的内容移动到指定的位置进行显示,无论它所在的组件结构有多深
vue<teleport to="移动位置"> <div v-if="isShow" class="mask"> <div class="dialog"> <h3>我是一个弹窗</h3> <button @click="isShow = false">关闭弹窗</button> </div> </div> </teleport>- to 的值可以是 id
Suspense
等待异步组件时渲染一些额外内容,让应用有更好的用户体验
使用步骤:
异步引入组件
jsimport { defineAsyncComponent } from "vue"; const Child = defineAsyncComponent(() => import("./components/Child.vue"));使用
Suspense包裹组件,并配置好default与fallbackvue<template> <div class="app"> <h3>我是App组件</h3> <Suspense> <template v-slot:default> <Child /> </template> <template v-slot:fallback> <h3>加载中.....</h3> </template> </Suspense> </div> </template>
只有使用异步引入,使用该组件标签的情况下,组件返回值,才能写成如下形式,否则页面无法显示该组件!

响应式原理
vue2 的响应式
实现原理:
对象类型:通过
Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
jsObject.defineProperty(data, "count", { get() {}, set() {}, });
存在问题:
- Object.defineProperty() 捕获不到 新增属性、删除属性的操作, 无法直接相应式更新界面。
- 直接通过下标修改数组, 界面不会自动更新。
Vue3 的响应式
实现原理:
通过 Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
通过 Reflect(反射): 对源对象的属性进行操作。
MDN 文档中描述的 Proxy 与 Reflect:
Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
Reflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
jsnew Proxy(data, { // 拦截读取属性值 get(target, prop) { return Reflect.get(target, prop); }, // 拦截设置属性值或添加新属性 set(target, prop, value) { return Reflect.set(target, prop, value); }, // 拦截删除属性 deleteProperty(target, prop) { return Reflect.deleteProperty(target, prop); }, }); proxy.name = "tom";
模拟 Vue3 响应式数据原理
Proxy 代理对象
- Proxy 构造函数 参数
- 参数 1:源数据
- 参数 2:配置对象,get、set、deleteProperty 函数
- 不配置这些函数时,默认就会达成相应操作
- get 函数 读取数据
- 参数 1:new Proxy 时传入的源对象
- 参数 2:读取的属性名
- set 函数 **修改数据 ** 新增
- 参数 1、参数 2 同上 get 函数
- 参数 3:修改后的值
- deleteProperty 函数 删除数据
- 参数同 get 函数
// 原数据
let person = {
name: "张三",
age: 18,
};
const p = new Proxy(person, {
get(target, propName) {
console.log(`读取了person的${person}值`);
return Reflect.get(targrt, propName);
},
set(target, propName, value) {
console.log(`修改了person的${propName}值,改变为${value}`);
return Reflect.set(targrt, propName, value);
},
deleteProperty(target, propName) {
return Reflect.deleteProperty(target, propName);
},
});
// 增删改查时调用Proxy底层的Handler
// 此时通过p修改属性值,会导致person的值一起改,实现代理操作Reflect 反射对象
- Reflect 优点
- 重复追加同一个属性,
- Object.defineProperty 对导致代码出错挂掉
- Reflect.defineProperty 不会导致代码挂掉,代码依旧能执行,会返回 true/false 告知是否成追加成功
- 只有第一次成功返回 ture,第二次失败返回 false
- 重复追加同一个属性,
// 使用window身上的Reflect对象
let obj = { a: 1, b: 2 };
// 读取obj.a
Reflect.get(obj, "a"); // 1
// 修改obj.a
Reflect.set(obj, "a", 666); // true
// 删除obj.a
Reflect.deleteProperty(obj, "a"); // true
// 追加obj.c
Reflect.defineProperty(obj, "c", {
get() {
return 4;
},
});Composition API 优势
Options API - 命令式 api 存在的问题
- 使用传统 OptionsAPI 中,新增或者修改一个需求,就需要分别在 data,methods,computed 里修改 。
Composition API - 声明式 api 的优势
- 可以更加好的组织代码、函数。将同一功能的相关代码放在一起。
<script setup>
是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。当同时使用 SFC 与组合式 API 时该语法是默认推荐。相比于普通的
<script>语法,它具有更多优势:
优势:
- 更少的样板内容,更简洁的代码。
- 能够使用纯 TypeScript 声明 props 和自定义事件。
- 更好的运行时性能 (其模板会被编译成同一作用域内的渲染函数,避免了渲染上下文代理对象)。
- 更好的 IDE 类型推导性能 (减少了语言服务器从代码中抽取类型的工作)。
特点:
类似与 React 的 render?
里面的代码会被编译成组件
setup()函数的内容。这意味着与普通的<script>只在组件被首次引入的时候执行一次不同,<script setup>中的代码会在每次组件实例被创建的时候执行。任何在
<script setup>声明的顶层的绑定 (包括变量,函数声明,以及 import 导入的内容) 都能在模板中直接使用:import 导入的内容也会以同样的方式暴露。这意味着我们可以在模板表达式中直接使用导入的 helper 函数,而不需要通过
methods选项来暴露它:vue<script setup> import { toRefs } from "vue"; // 相比于 <script> 不需要再通过return返回内容,直接使用 import { capitalize } from "./helpers"; const props = defineProps({ // props text: String, message: Number, }); const { text } = toRefs(props); // 保留text响应式 const msg = "Hello!"; // 变量 function log() { console.log(props.text, msg); } // 函数 </script> <template> <button @click="log">{{ msg }}</button> <div>{{ capitalize("hello") }}</div> </template>
全家桶
Vuex
模块化写法同 VUEX3
npm install vuex -S // 下载
// 新建store文件夹 index.js
import { createStore } from 'vuex'
export default createStore({
state(){
return {
key:value,
xxx:xxxx,
}
},
mutations:{
functionname(state,传参1,...){
state.key=xxxx
}
}
})
// main.js
import store from './store/index.js'
app.use(store) // app是 createApp(App)
// 组件中使用 store中的数据,不需要this
<p :xxx="$store.state.xxx" />
// 组件中调用 mutations
// 在setup中使用store时也需采用该方法
import { useStore } from 'vuex'
setup(){
let store = useStore()
...
// 在触发的方法中写commit
store.commit('mutation中的名字',)
}Pinia
Vuex 的迭代方案,与 Vuex 的 API 类似,作者是 Vue.js 及 Vuex 的核心成员之一。
- 独特之处
- 直观,像定义 components 一样地定义 store
- 去除 mutations,只有 state、getters、actions
- 不分同步异步,更完整的 TypeScript 支持
- Vue 浏览器插件支持 Pinia,提供更好的开发体验
- 能够构建多个 stores,可直接分模块化的使用,并自动的代码拆分
- 兼容 Vue2、Vue3 及其轻量(1kb)
// 安装pinia
yarn add pinia
// Vue3导入并应用pinia main.js入口文件
import { createPinia } from 'pinia'
const pinia = createPinia()
app.use(pinia)
// 定义数据仓库 src/store/index.js
import { defineStore } from 'pinia'
import axios from 'axios'
// 传入数据仓库的id命名、仓库配置项
export default defineStore("仓库名",{
// 定义静态数据
state:()=>{
return {
count:10,
timuList:[],
list:[{
name:"phone",
money:6666
},{
name:"ipad",
money:9999
}]
}
},
// 定义计算属性
getters:{
sumPrice:(state)=>{ // 求所有商品的总价值
return state.list.reduce((pre,item)=>{
return per+item.money
})
}
},
// 定义action 修改数据的方法,异步同步都可以
actions:{
async getTimu(){
let result = await axios.get("http://xxx.com/")
this.timuList = result.data
}
}
})
// 组件中使用 store
// 1. 导入数据对象
import useMainStore from './stort/index'
let store = useMainStore()
// 2.在标签中使用
` <p>{{ store.count }}</p> `
// 3.1修改store 借助解构赋值、storeToRefs()
import { storeToRefs } from 'pinia'
let { count } = storeToRefs(store)
function handleClick(){
count.value++ // 此时可以通过解构赋值得到的变量修改操作store中的store
}
// 3.2 修改store 使用$patch:对象式/函数式
function handleClick(){
store.list.push({name:"watch",money:2000})
store.$patch({
count:store.count++,
list:store.list,
}) // 相比于3.1 可同时修改多个数据
}
store.$patch((state)=>{
store.count += 10;
store.list.push({
name:"watch",
money:2000
})
})
// 4.修改store 将现在的整个state进行替换
store.$store={
count:100,
list:[
{ name:"watch",money:2000 },
{ name:"watch2",money:2100 },
]
}
// 5.重置store 恢复为初始状态
store.$reset()
// 6.监听整个仓库变化
store.$subscribe((mutation,state)=>{
console.log(mutation)
console.log(state)
})
// 7.调用axios的方法,获取异步数据
store.getTimu()// store 数据使用时响应式问题:
// 直接解构使用时,数据不具备响应式,只能获取第一次默认的值
// 解决方法1:使用 Pian 的 storeToRefs() 方法
// 得到的数据在script中使用需要带 .value
// template中使用不需要带 .value
import useMainStore from "./stort/index";
let store = useMainStore();
import { storeToRefs } from "pinia";
let { count } = storeToRefs(store);持久化存储
问题:pinia/vuex 管理的状态,在手动刷新浏览器时,状态管理的数据都会丢失,被初始化,需要进行持久化存储,常用方式是 sessionStroage 或 localStorage 手动保存,或借助第三方插件完成。
- 方案 1:pinia-plugin-persistedstate (prazdevs.github.io)
- 方案 2:Getting started | Pinia Plugin Persist (seb-l.github.io)
router 4
基本使用
- 与 router 3 大致一致
// 1.安装 npm i vue-router
// router文件夹下 index.js
import { createRouter,createWebHashHistory } from 'vue-router'
const routes = [{
path:'/',
component:()=>import('../views/Main.vue'),
redirect:'/home',
children:[
{
path:'./home',
name:'home'
component:()=>import('../views/home/Home.vue'),
},
]
}]
const router = createRouter({
history:createWebHashHistory(), // # hash模式
// history:createWebHistory(), // H5模式
routes
})
export default router
// main.js
.use(router)
// 在组件中使用
import { useRouter } from 'vue-router';
const router = useRouter()
// 使用
router.push({
name:'xxx'
})动态添加路由
import { authentication } from "../api/common";
import { useRouter } from "vue-router";
const router = useRouter();
let token = location.href.slice(location.href.indexOf("id_token=") + 9);
authentication(token).then((res) => {
if (res == 200) {
const childRouter = {
path: "/home",
component: () => import("../components/Tabs.vue"),
name: "home",
};
router.addRoute("home", childRouter); // 这里的意思是将这个路由添加到根目录下,也就是在根目录下创建一个路由
router.push({ path: "/home" });
}
});useRouter/useRoute 区别
- 作用:
useRouter返回路由器实例,useRoute返回当前的路由地址。
useRouter提供了全局路由信息,如.push(),.replace(),.go(),.back(),.forward()等方法useRoute提供了当前路由的信息,如.path,.hash,.query,.params,.fullPath等。
axios 封装
携带必要的请求信息,处理同样的相应状态码等,逻辑一致时,进行封装统一操作
const NETWORK_ERROR = "网络请求出错,稍后再试";
import axios from "axios";
// 创建axios实例对象
const service = axios.create({
baseURL: "xxxx.xxx",
timeout: 5000, // 设置超时时间
});
// 在请求之前进行处理 axios拦截器
service.interceptors.request.use((req) => {
// 在请求之前自定义header
// jwt-token认证的事情
}),
(err) => {
return Promise.reject(err);
};
// 请求相应回来时,先做一些处理
service.interceptors.response.use((res) => {
// 根据接口规范,获取相应的数据,执行特定操作
const { code, data, msg } = req.data;
if (code == 200) {
return data;
} else {
// 网络请求错误,进行全局提示?
return Promise.reject(msg || NETWORK_ERROR);
}
});
// 封装的核心函数
function request(options) {
options.method = options.method || "get";
if (options.method.toLowerCase() == "get") {
options.params = options.data;
}
return service(options);
}
export default request;- api.js 整个项目的 api 管理
// 整个项目的api管理
import request from './request.js'
export default {
// 获取左侧的菜单的接口
getTableDate(params){
return request({
url:'/home/getTableDate',
mathod:'get',
data:params
})
},
getxxxxx(){...},
getxxxxx(){...},
....
}
// 将api挂载到全局中 main.js
import api from './api/api.js'
app.config.globalProperties.$api = api // app 是 createApp(App)
// 在组件中调用
import { getCurrentInstance } from 'vue'
const {proxy} = getCurrentInstance(); // proxy类似于vue2中的this
proxy.$api.getTableDate(params).then(()=>{
})进阶内容
Vue 模板解析
vue 模板 -> AST -> render 函数 -> vnode
- AST 用一个 JSON 对象来描述一个元素信息
- vnode 同样用来标识模板,优势在于进行 diff 对比时性能更好
- 优化真实 dom 生成的过程
define 宏
defineOptions
Vue3.3+中新增的新特性
- 作用
- name 提供组件命名方式
- 默认需使用 传统 name 写法 或借助插件在 script 中 写 name 值
- 通过
defineOptions宏定义 name
- inheritAttrs 穿透配置
- 根标签存在多个,那么透传将失效
- 默认开启,可手动配置禁用传值穿透
- name 提供组件命名方式
//
组件的默认组件名为.vue单文件组件[SFC]文件的名字,如果需要修改组件名则需要结合Options
API进行配置
<!-- src/components/Com.vue -->
<script>
export default {
name: "ComponentName", // 传统写法
inheritAttrs: false,
};
</script>
<script setup>
defineOptions({
name: "ComponentName",
inheritAttrs: false,
});
</script>defineProps
defineEmits
defineExpose
使用
<script setup>语法糖的组件是默认关闭的,即通过ref或$parent获取组件,拿不到任何在<script setup>中声明的绑定使用 defineExpose 暴露需要外界使用的属性和方法
<script setup>
/* 子组件 */
import { ref } from "vue";
const name = ref("张三");
const sayName = () => {
console.log("我叫 " + name.value);
};
defineExpose({ name, sayName });
</script>
// 父组件
<template>
<Child ref="child"></Child>
</template>
<script setup>
import { ref, onMounted } from "vue";
const child = ref(null);
onMounted(() => {
console.log(child.value.name); // "张三"
child.value.sayName(); // "我叫张三"
});
</script>其他内容
IDE 及插件
- VsCode
- 强烈推荐 Visual Studio Code (VSCode),因为它对 TypeScript 有着很好的内置支持。
- Volar 是官方的 VSCode 扩展,提供了 Vue 单文件组件中的 TypeScript 支持,还伴随着一些其他非常棒的特性。
- 注意:Volar 取代了我们之前为 Vue 2 提供的官方 VSCode 扩展 Vetur。如果你之前已经安装了 Vetur,请确保在 Vue 3 的项目中禁用它。
- WebStorm 对 TypeScript 和 Vue 也都提供了开箱即用的支持
tag 实战步骤
- 首页的 tag 一开始就会存在,而且是不能进行删除的
- 当点击左侧栏的时候,如果 tag 没有该菜单名称则新增并置为蓝色,如果有则当前 tag 背景变为蓝色
- 删除当前 tag,如果是最后一个,那么路由调整为前一个标签并背景变蓝;如果不是最后一个那么路由调整为后一个标签并且背景变蓝
Mock 模拟数据
- 本地Mock
// 下载mock插件
npm i mockjs -S
// 新建 api/mockData 文件夹 home.js
export default {
getHomeDate:()=>{
return {
code:200,
data:{
tableDate:[{...},{...},...]
}
}
}
}
// api文件夹下 mock.js
import Mock from 'mockjs' // 引入mock
import homeApi from './mockData/home.js'
Mock.mock('/home/getData',homeApi.getHomeDate) //拦截路径并传入数据(例:'/home/getData')
// 在代码中使用axios get请求 /home/getData 接口即可拿到数据- 在线 mock
Element+
安装
sh# 选择一个你喜欢的包管理器 # NPM $ npm install element-plus --save # Yarn $ yarn add element-plus # pnpm $ pnpm install element-plus完整引入
js// main.js import { createApp } from "vue"; import ElementPlus from "element-plus"; import "element-plus/dist/index.css"; import "./style.css"; import App from "./App.vue"; createApp(App).mount("#app").use(ElementPlus);按需自动引入(代码中可直接使用)
js// 安装unplugin-vue-components 和 unplugin-auto-import这两款插件 npm install -D unplugin-vue-components unplugin-auto-import // vite 插入配置 vite.config.js import { defineConfig } from 'vite' import AutoImport from 'unplugin-auto-import/vite' import Components from 'unplugin-vue-components/vite' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' export default defineConfig({ // ... plugins: [ // ... AutoImport({ resolvers: [ElementPlusResolver()], }), Components({ resolvers: [ElementPlusResolver()], }), ], }) //webpack 插入配置,参看官网手动引入(麻烦)
ECharts
// 1.下载 echarts
npm install echarts -S
// 引入
// 2.1 完全引入
import * as echarts from 'echarts';
// 2.2 按需引入 main.js (建议单独建文件进行管理)
// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
import * as echarts from 'echarts/core';
// 引入柱状图图表,图表后缀都为 Chart
import { BarChart } from 'echarts/charts';
// 引入提示框,标题,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component
import {
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent
} from 'echarts/components';
// 标签自动布局,全局过渡动画等特性
import { LabelLayout, UniversalTransition } from 'echarts/features';
// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
import { CanvasRenderer } from 'echarts/renderers';
// 注册必须的组件
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
BarChart,
LabelLayout,
UniversalTransition,
CanvasRenderer
]);
// 在组件中进行使用 跟之前一样,初始化图表,设置配置项,看官网实例
var myChart = echarts.init(document.getElementById('main'));
myChart.setOption({
// ...
});动态引入
动态引入图片
- 如下图,使用时直接掉用 getImgSrc 方法

动态引入组件
借助
<component :is='xxx'>由于组件是通过变量引用而不是基于字符串组件名注册的,在
<script setup>中要使用动态组件的时候,应该使用动态的:is来绑定:请注意组件是如何在三元表达式中被当做变量使用的。
vue<script setup> import Foo from "./Foo.vue"; import Bar from "./Bar.vue"; </script> <template> <component :is="Foo" /> <component :is="someCondition ? Foo : Bar" /> </template>vue 插件
- Vuetify:开源组件库
- NuxtJS:
- VueX、Piano
- Vue-persistedState
- VuePess
- Vue meta
- Vue- chartjs
- Vue grid layout
- VeeValidate
- TroisJS
- 自定义滚动库——iScroll
- 移动端 UI:VantUI、cube-ui Document (didi.github.io)、cube-ui、Vuetify、
- PC 端 UI:Element UI、iView、AT UI、beauty2、Buefy
使用记录
markRaw 或者 shallowRef 代替 ref
自定义组件之 v-model
Vue 父子组件间双向数据绑定:v-model 与 dineModel 详解 - 雨月空间站 (mintimate.cn)
引入组件方式
// Unknown variable dynamic import: ../views/ 解决
let modules = import.meta.glob("../views/BlogGather/**/**/*.vue");
console.log(modules);
export const routes = [
{
name: "index",
path: "/",
component: () =>
import(`../views/BlogGather/IndexPage/${path}IndexPage.vue`),
meta: { title: "博客" },
},
{
name: "xxx",
path: "/xxxx",
component: modules[`../${path}.vue`],
meta: { title: "详情页面" },
},
];动态设置组件 name
需求场景:系统路由根据接口返回,其中包含对应路由是否缓存的配置;对于缓存配置,keep-alive 是通过组件 name 而非 router 的 name 进行动态配置;因此为了确保每个想要缓存的路由信息都是唯一的,避免出现缓存冲突。在动态路由生成时对组件进行动态命名
let modules = import.meta.glob("../views/BlogGather/**/**/*.vue");
const comp = modules[`../${component}.vue`];
comp().then((res: any) => {
res.default.name = permissionId;
});tabs 页签
锚点定位及跳转
4 种方法实现 html 页面内锚点定位及跳转项目中经常会出现点击跳转锚点的方法,比如给一个 a 标签一个 href=“#锚点”, - 掘金 (juejin.cn)

