前端 - Vue3
创建Vue3工程
vue-cli
vue create test_vue3
分析工程结构
/* main.js */
import { createApp } from 'vue' // 引入的不再是Vue构造函数, 而是一个createApp工厂函数
import App from './App.vue'
createApp(App).mount('#app') // 创建应用实例对象app, 类似Vue2的vm, app更轻量
<template>
<!-- 模板结构可以没有根标签 -->
<h1>Hello App</h1>
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld msg="Welcome to Your Vue.js App" />
</template>
vite
## 创建工程
npm init vite-app <project-name>
## 进入工程目录
cd <project-name>
## 安装依赖
npm install
## 运行
npm run dev
常用Composition API
setup
setup()
是Vue3中一个新的配置项,值为一个函数,组件中所用到的:数据、方法等等,均要配置在setup
中。
注意:尽量不要与Vue2.x配置混用
- Vue2.x配置(
data
、methos
、computed
...)中可以访问到setup
中的属性、方法 - 但在
setup
中不能访问到Vue2.x配置(data
、methos
、computed
...) - 如果有重名,
setup
优先
setup特点
setup()
在beforeCreate()
之前执行一次,this
是undefined
。
此外,setup()
还会接受两个参数:
-
props
:值为对象,包含组件外部传递过来,且组件内部声明接受的属性 -
context
:上下文对象attrs
:值为对象,用来接收组件外部传递过来,但是没有通过props
声明的属性slots
:收到的插槽内容,相当于之前使用的this.$slots
(此时setup
的this
是undefined
)emit
:分发自定义事件的函数,相当于之前使用的this.$emit
<!-- App.vue -->
<Demo msg="test props" :num="123">
<template v-slot:test>
<p>Test slot!</p>
</template>
</Demo>
export default {
props: ["msg"], // 只声明接收msg
setup(props, context) {
console.log(props); // Proxy {msg: 'test props'}
console.log(context.attrs); // Proxy {num: 123, __vInternal: 1}
context.emit('eventTest', 123); // 触发事件
return {};
},
};
ref函数
用来定义一个响应式的数据:
const xxx = ref(initValue);
ref()
返回一个包含响应式数据的引用对象,即下面代码中的RefImpl
对象:
<h3>Name: {{ name }}</h3>
<h3>Age: {{ age }}</h3>
<h3>工作: {{ job.type }}, 薪水:{{ job.salary }}</h3>
<button @click="changeInfo">Change info</button>
import { ref } from "vue";
export default {
name: "App",
components: {},
setup(props) {
const name = ref("Lee");
const age = ref(30);
const job = ref({ type: "Front-end", salary: 800 });
function changeInfo() {
// 基本数据类型的输出
name.value = "Zhang";
age.value = 50;
console.log(name); // RefImpl {...}
console.log(name.value); // Zhang
// 对象的输出
job.value.salary = 400;
console.log(job); // RefImpl {...}
console.log(job.value); // Proxy {type: 'Front-end', salary: 400}
}
return {
name,
age,
job,
changeInfo,
};
},
};
操作方法:
- JS中操作数据:
xxx.value
- 模板中读取数据: 不需要.value,直接:
<div>{{xxx}}</div>
注意:接收的数据:基本类型、对象类型
- 基本类型:响应式依然是靠
Object.defineProperty()
的get
与set
完成的 - 对象类型:响应式依靠Vue3的新函数
reactive()
来实现
reactive函数
用来定义一个对象类型的响应式数据,不能定义基本数据类型。返回一个包含响应式数据的代理对象,即Proxy
实例对象。
- 内部基于 ES6 的
Proxy
实现,通过代理对象操作源对象内部数据进行操作
import { ref, reactive } from "vue";
export default {
name: "App",
setup(props) {
// ...
const job = reactive({ type: "Front-end", salary: 800 });
const arr = reactive(['hello', 123, false]);
function changeInfo() {
// ...
// 不需要通过value修改
job.salary = 400;
console.log(job); // Proxy {type: 'Front-end', salary: 400}
// 可以直接通过索引修改, Vue2中无法响应
arr[0] = 'test';
console.log(arr); // Proxy {0: 'test', 1: 123, 2: false}
}
return {...};
},
};
Vue3实现响应式
回顾Vue2
实现原理:
- 对象:通过
Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持) - 数组:对数组的变更方法进行包裹
存在问题:
- 对象:新增属性、删除属性、界面不响应
- 数组:直接通过下标修改数组,界面不响应
Vue3原理
使用window.Proxy
和window.Reflect
实现。
const person = { name: 'Lee', age: 30 }; // 源数据
/* 模拟Vue3实现响应式 */
const p = new Proxy(person, {
// p的属性被读取时调用
get(target, propName) {
console.log(target === person); // true
console.log(`p的属性${propName}被读取`);
// return target[propName];
return Reflect.get(target, propName);
},
// p的属性被修改、或新增属性时调用
set(target, propName, value) {
console.log(`p的属性${propName}被修改`);
// 更新页面操作...
// target[propName] = value;
Reflect.set(target, propName, value);
},
deleteProperty(target, propName, value) {
console.log(`p的属性${propName}被删除`);
// 更新页面操作...
// return delete target[propName];
return Reflect.deleteProperty(target, propName, value);
}
});
ref()和reactive()
ref()
用来定义基本数据类型,通过Object.defineProperty()
的get
与set
来实现响应式(数据劫持)reactive()
通过使用Proxy
来实现响应式(数据劫持), 并通过Reflect
操作源对象内部的数据
computed
import {computed} from 'vue'
setup(){
...
//计算属性——简写
let fullName = computed(()=>{
return person.firstName + '-' + person.lastName
})
//计算属性——完整
let fullName = computed({
get(){
return person.firstName + '-' + person.lastName
},
set(value){
const nameArr = value.split('-')
person.firstName = nameArr[0]
person.lastName = nameArr[1]
}
})
}
watch
监视ref
<h2>当前求和:{{ sum }}</h2>
<button @click="sum++">sum++</button>
<h2>当前求和:{{ sum2 }}</h2>
<button @click="sum2--">sum2--</button>
export default {
name: "Demo",
setup() {
let sum = ref(0);
let sum2 = ref(0);
// watch(sum, (newVal, oldVal) => {
// console.log("sum的值变了", newVal, oldVal); // sum的值变了 1 0
// });
watch([sum, sum2], (newVal, oldVal) => {
console.log("sum或sum2的值变了", newVal, oldVal); // sum或sum2的值变了 [0, -1] [0, 0]
}, {immediate: true});
return { sum, sum2 };
},
};
监视reactive
- 强制开启
deep
,且无法通过{deep: false}
关闭 - 无法正确获取
oldVal
,获取到的oldVal
和newVal
相同,可以通过分别监视对象的每个属性解决 - 监视
reactive
定义的响应式数据中某个属性时,{deep: true}
配置有效,例如监视person
的某个对象类型的属性watch(() => person.salary, (...) => {...}, {deep: true})
,如果不配置deep
,无法监视到变化
export default {
name: "Demo",
setup() {
let person = reactive({ name: "Lee", age: 30 });
watch(person, (newVal, oldVal) => {
console.log("person的值变了", newVal, oldVal);
}); // person的值变了 Proxy {name: 'Lee', age: 31} Proxy {name: 'Lee', age: 31}
// watch([() => person.name, () => person.age], (newVal, oldVal) => {
// console.log("person的值变了", newVal, oldVal); // sum的值变了 1 0
// });
return { sum, sum2, person };
},
};
watchEffect
- 不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性
watchEffect(() => {
const x = person.age;
console.log('person的age属性变化了');
});
生命周期
- Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:
beforeDestroy
改名为beforeUnmount
destroyed
改名为unmounted
- Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
beforeCreate
==>setup()
created
==>setup()
beforeMount
==>onBeforeMount
mounted
==>onMounted
beforeUpdate
===>onBeforeUpdate
updated
==>onUpdated
beforeUnmount
==>onBeforeUnmount
unmounted
==>onUnmounted
hook
- 本质是一个函数,把
setup
函数中使用的Composition API进行了封装 - 类似于vue2.x中的mixin
- 自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂
toRef
创建一个 ref
对象,能够将一个响应式对象中的某个属性单独提供给外部使用,并且仍然是响应式的,用法:
const name = toRef(person,'name');
也可以将对象的每个属性都做这样的操作,并将结果以数组形式返回:
const arr = toRefs(person);
其他Composition API
shallowReactive 与 shallowRef
-
shallowReactive
:只处理对象最外层属性的响应式(浅响应式) -
shallowRef
:只处理基本数据类型的响应式,不进行对象的响应式处理 -
什么时候使用:
- 如果有一个对象数据,结构比较深,但变化时只是外层属性变化 ==>
shallowReactive
- 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ==>
shallowRef
- 如果有一个对象数据,结构比较深,但变化时只是外层属性变化 ==>
readonly 与 shallowReadonly
readonly
: 让一个响应式数据变为只读的(深只读)shallowReadonly
:让一个响应式数据变为只读的(浅只读)
toRaw 与 markRaw
toRaw
:将一个由reactive
生成的响应式对象转换为原始的普通对象- 用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新
markRaw
:标记一个对象,使其永远不会再成为响应式对象- 有些值不应被设置为响应式的,例如复杂的第三方类库等
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能
customRef
创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收
track
和trigger
函数作为参数,并且应该返回一个带有get
和set
的对象
provide 与 inject
用于祖先和后代之前的通信。
响应式数据判断
isRef
:检查一个值是否为一个ref
对象isReactive
: 检查一个对象是否是由reactive
创建的响应式代理isReadonly
:检查一个对象是否是由readonly
创建的只读代理isProxy
:检查一个对象是否是由reactive
或者readonly
方法创建的代理
新的组件
Fragment
在Vue3中,组件可以没有根标签,,内部会将多个标签包含在一个Fragment
虚拟元素中。
Teleport
将html结构移动到指定位置,例如下面,直接移动到<body>
标签:
<teleport to="body">
<div v-if="isShow" class="mask">
<div class="dialog">
<h3>我是一个弹窗</h3>
<button @click="isShow = false">关闭弹窗</button>
</div>
</div>
</teleport>
Suspense
等待异步组件时渲染一些额外内容,让应用有更好的用户体验,使用步骤:
- 异步引入组件
import {defineAsyncComponent} from 'vue'
const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
- 使用
Suspense
包裹组件,并配置好default
与fallback
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!