🫵认识vue3.0
- ue3.0发布时间为2020-9-18,从项目体验上,vue3.0比起vue2.0有以下优势
打包大小减少41%
初次渲染块55%,更新渲染块133%
内存占比少54%
🫵创建vue3.0
- 使用vue-cli
- 需要确保vue-cli版本在4.5.0以上。
vue create vue-name 选择vue3.0项目
- 使用vite
- 注意:使用 Vite 需要 12.x 以上的 Node.js 版本。
npm init vue@latest
npm init vite@latest
项目结构
使用的为vue-cli创建的项目
- src\main.js
// 引入的为一个名为createApp的工厂函数,不再是Vue构造函数
import { createApp } from 'vue'
import App from './App.vue'
// 创建应用实例对象,类似于Vue2中的vm,但是更“轻”,并挂载根标签
createApp(App).mount('#app')
- src\App.vue
<template>
<!-- Vue3组件中的模板结构可以没有根标签 -->
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</template>
Vue3开发者工具的安装 及引用
🫵组合式API
🌀setup函数
- vue3.0使用setup作为vue的配置项,数据、方法等都配置在setup里面,也就是说vue2.0中全部的配置都配置在setup里面
- setup是一个函数 ,避免使用this,没有指向 组件实例
- setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)
<template>
</template>
<script>
export default{
setup(){
return{}
}
}
</script>
setup函数参数
- setup会在beforeCreate之前调用一次,此时this为underfined
setup函数的两种返回值
- 返回值为一个对象,该对象中的属性、方法, 在模板template中均可以直接使用
<template>
<h1>个人介绍</h1>
<h2>name: {{name}}</h2>
<h2>age: {{age}}</h2>
<button @click="sayHello">打招呼</button>
</template>
<script>
export default {
name: 'App',
// 测试setup, 不考虑响应式
setup() {
// 数据
let name = 'ZS'
let age = 22
// 方法
function sayHello() {
alert(`my name is ${name}, I am ${age}, hello`)
}
// 返回一个对象
return {
name,
age,
sayHello
}
}
}
</script>
- 若返回一个渲染函数:则可以自定义渲染内容
<template>
<h1>个人介绍</h1>
<h2>name: {{name}}</h2>
<h2>age: {{age}}</h2>
<h2>sex: {{sex}}</h2>
<button @click="sayHello">打招呼 vue3</button>
<button @click="sayWelcome">打招呼 vue2</button>
</template>
<script>
export default {
name: 'App',
data() {
return {
sex: 'male'
}
},
methods: {
sayWelcome() {
alert('hello world vue2')
}
},
// 测试setup, 不考虑响应式
setup() {
// 数据
let name = 'ZS'
let age = 22
// 方法
function sayHello() {
alert(`my name is ${name}, I am ${age}, hello`)
}
// 返回一个对象
return {
name,
age,
sayHello
}
}
}
</script>
vue2 vue3混用注意
- 在Vue3中仍然可以使用Vue2的配置项
- setup尽量不要与Vue2.x配置混用
- Vue2.x配置(data、methos、computed…)中可以访问到setup中的属性、方法
- 但在setup中不能访问到Vue2.x配置(data、methos、computed…)内容会是underfined。
- Vue2配置项如果与setup中的属性或方法冲突,setup优先
🌀script setup语法糖
- 更好的代码提示
- 能够使用纯 Typescript 声明 prop 和抛出事件
- 更好的运行时性能
<script setup>
</script>
<template>
</template>
- 任何在
<script setup>
声明的顶层的绑定 (包括变量,函数声明,以及 import 引入的内容) 都能在模板中直接使用 - 导入的组件直接使用
🌀reactive函数
- 作用: 定义一个任意引用类型 的 响应式数据(基本类型不要用它,要用ref函数)
- 语法:
const 代理对象= reactive(源对象)
接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象) - reactive定义的响应式数据是“深层次的”。
- 内部原理基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作
import {reactive} from 'vue'
const pageInfo = reactive({current:1,pageSize:5});
🌀ref函数
- 作用: 定义一个响应式的数据
- 语法:
const xxx = ref(initValue)
- 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)。
- setup 函数内部(JS中)操作数据:
xxx.value
- 模板中读取数据: 不需要.value,Vue会自动进行解包操作,直接:
<div>{{xxx}}</div>
- 注意:
- 接收的数据可以是:基本类型、也可以是对象类型
- 基本类型的数据:靠Object.defineProperty()的get与set,实现响应式(数据劫持)
- 对象类型的数据:内部会自动通过reactive(window.Proxy)转为代理对象
import {ref} from 'vue'
let age = ref(22)
reactive 和 ref 的区别
- 从定义数据角度对比:
- ref 定义:基本类型数据
- reactive 定义:对象(或数组)类型数据
- 注意:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象
- 从原理角度对比:
- ref 定义基本数据类型 通过Object.defineProperty()的get与set来实现响应式(数据劫持)
- reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据
- 从使用角度对比:
- ref定义数据:操作数据需要.value,读取数据时模板中直接读取不需要.value
- reactive定义的数据:操作数据与读取数据:均不需要.value
- 一般将数据封装在一个data对象中,利用reactive函数将该对象变为响应式数据对象
🌀响应式原理
- 通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等
get(target, propName):target原对象,propName读取的属性名
set(target, propName, value):target原对象,propName修改的属性名,value修改成的属性值,target[propName]原属性值
deleteProperty(target, propName):target原对象,propName删除的属性名,
//源数据
let person = {
name:'张三',
age:18
}
//模拟Vue3中实现响应式
//Proxy对属性的增删改查都可以监测得到
//#region
const p = new Proxy(person,{
//有人读取p的某个属性时调用
get(target,propName){
console.log(`有人读取了p身上的${propName}属性`)
return target[propName]
},
//有人修改p的某个属性、或给p追加某个属性时调用
set(target,propName,value){
console.log(`有人修改了p身上的${propName}属性,我要去更新界面了!`)
target[propName] = value
},
//有人删除p的某个属性时调用
deleteProperty(target,propName){
console.log(`有人删除了p身上的${propName}属性,我要去更新界面了!`)
return delete target[propName]
}
})
- 通过Reflect(反射): 对源对象的属性进行操作
- 使用反射对数据进行增删改,(很多Object的方法都逐渐移到Reflect上,主要是如果同名的时候,反射会有个返回值告诉是否操作成功)
// Reflect(反射)
// 读取对象指定属性的值
Reflect.get(object, '属性名')
// 修改对象指定属性的值
Reflect.set(object, '属性名', '新属性值')
// 删除对象指定属性
Reflect.deleteProperty(object, '属性名')
//模拟Vue3中实现响应式
//#region
const p = new Proxy(person,{
//有人读取p的某个属性时调用
get(target,propName){
console.log(`有人读取了p身上的${propName}属性`)
return Reflect.get(target,propName)
},
//有人修改p的某个属性、或给p追加某个属性时调用
set(target,propName,value){
console.log(`有人修改了p身上的${propName}属性,我要去更新界面了!`)
Reflect.set(target,propName,value)
},
//有人删除p的某个属性时调用
deleteProperty(target,propName){
console.log(`有人删除了p身上的${propName}属性,我要去更新界面了!`)
return Reflect.deleteProperty(target,propName)
}
})
🌀响应式数据的判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由 reactive 创建的响应式代理
- isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
- isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理
💥shallowReactive和shallowRef
-
shallowReactive:只处理对象最外层属性的响应式(浅响应式)
-
shallowRef:只考虑基本数据类型的响应式,不考虑引用类型的响应
-
什么时候使用?
- 如果有一个对象数据,结构比较深, 但变化时只是最外层属性变化 ===> shallowReactive。
- 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生成新的对象来替换,即对于该对象数据在后续功能中不会修改其属性,而是会将该对象整个进行替换 ===> shallowRef。
💥readonly和shallowReadonly
- readonly 与 shallowReadonly 均接收一个响应式数据为参数
- 普通对象也行,只不过不是响应式了
- readonly: 让一个响应式数据变为只读的(深只读),传入的响应式数据不管有几层,都不能进行修改
- shallowReadonly:让一个响应式数据变为只读的(浅只读),传入的响应式数据只有最外层数据不能进行修改
- 应用场景: 不希望数据被修改时。将数据交给其他组件并且不希望这个组件对数据进行更改
import {readonly,shallowReadonly} from 'vue'
let data = reactive({
name: 'xiaozhi',
age: 23,
});
data=readonly(data)
💥triggerRef
- 手动触发和 shallowRef 相关联的副作用
💥toRaw 与 markRaw
- toRaw:
- toRaw 接收一个响应式对象为参数,只能接收reactive生成的响应式对象,不能处理ref生成的响应式数据
- 作用:将一个由
reactive
生成的响应式对象转为普通对象。 - 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
- markRaw:
- 接收一个对象类型数据为参数
- 作用:标记一个对象,使其永远不会再成为响应式对象。向一个已经是响应式对象的数据追加一个属性,该属性的值为对象类型数据,vue会为其自动添加响应式,当不希望该属性的值为响应式时可以使用该函数,减小开销。
- 应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类库等,如果向响应式对象追加一个第三方类库对象(一般属性多且层次多),开销会很大。
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
💥toRef 和 toRefs
- 作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。
- 语法:
const name = toRef(obj,'name')
- 应用: 对reactive返回的对象进行解构获取值,响应式会失效,toRef要将响应式对象中的某个属性单独提供给外部使用时。
- 扩展:
toRefs
与toRef功能一致,但可以批量创建多个 ref 对象,创建一个对象中所有属性对应的 ref 对象,语法:toRefs(person)
<template>
<h4>{{person}}</h4>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
</template>
<script>
import {ref,reactive,toRef,toRefs} from 'vue'
export default {
name: 'Demo',
setup(){
//数据
let person = reactive({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})
const x = toRefs(person)
console.log('******',x)
//返回一个对象(常用)
return {
person,
// name:toRef(person,'name'),
// age:toRef(person,'age'),
// salary:toRef(person.job.j1,'salary'),
...toRefs(person)
}
}
}
</script>
💥unref
- 如果我们想要获取一个ref引用中的value,那么也可以通过unref方法
- 如果参数是一个 ref对象,则返回内部值value,否则返回参数本身;
- 这是 val = isRef(val) ? val.value : val 的语法糖函数;
🌀computed计算方法
- 返回一个不可变的响应式 ref 对象
<template>
<div>{{ data.num1 }}+{{ data.num2 }}={{ num }}</div>
<button @click="change1">加法</button>
<button @click="change2">减法</button>
</template>
<script setup>
import { reactive, computed } from 'vue';
let data = reactive({
num1: 9,
num2: 10
});
let num = computed(() => {
return data.num1 + data.num2;
});
function change1() {
data.num1 = data.num1 + 1;
}
function change2() {
data.num2 = data.num2 - 100;
}
}
};
</script>
<style scoped></style>
- 如果直接赋值 computed 属性,vue会有警告
报错:computed value is readonly
- 可以通过setter实现修改,但是不推荐
- 概念:当你在写一个函数时,其实就是在写函数的getter方法
let num = computed({
get() {
console.log('调用了');
return data.num1 + data.num2;
},
set(newValue, oldValue) {
console.log("修改调用");
}
});
- 此时其实计算方法 返回一个可变的(可读写)ref 对象,修改的话 也需要.value
💥在setup中 获取 ref
- 要定义一个ref对象,绑定到元素或者组件的ref属性上即可;
<template>
<!-- 1.获取元素 -->
<h2 ref="titleRef">我是标题</h2>
<button ref="btnRef">按钮</button>
<!-- 2.获取组件实例 -->
<show-info ref="showInfoRef"></show-info>
<button @click="getElements">获取元素</button>
</template>
<script>
import { ref, onMounted } from 'vue'
//子组件
import ShowInfo from './ShowInfo.vue'
export default {
components: {
ShowInfo
},
setup() {
const titleRef = ref()
const btnRef = ref()
const showInfoRef = ref()
// mounted的生命周期函数
onMounted(() => {
console.log(titleRef.value)
console.log(btnRef.value)
console.log(showInfoRef.value)
showInfoRef.value.showInfoFoo()
})
function getElements() {
console.log(titleRef.value)
}
return {
titleRef,
btnRef,
showInfoRef,
getElements
}
}
}
</script>
<style scoped>
</style>
🌀生命周期
- setup() 是围绕beforeCreate ,created 运行的,不需要显示定义了,代码写在 setup()中
<template>
</template>
<script setup>
import { onMounted } from 'vue'
onMounted( ()=>{ console.log("这个回调函数才是生命周期钩子函数") })
</script>
🌀Provide函数 / Inject函数
-
如果祖先元素的数据是具有响应式的,同时修改一方的数据,另一方的数据也会跟着变
-
祖先组件依赖:
setup(){
......
let car = reactive({name:'奔驰',price:'40万'})
// 给后代组件传递数据
// 第一个参数为对传递数据的命名,第二个参数为传递的数据
provide('car',car)
}
- 后代组件注入:
setup(props,context){
......
// 获取祖组件传递过来命名为car的数据
const car = inject('car',"这里是默认值")
return {car}
}
🌀watch函数
- watch(被监听的数据, 回调函数 ,{ 配置对象 })
- 其他可选参数:
- immediate:在侦听器创建时立即触发回调。第一次调用时旧值是 undefined。
- deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。
- flush:调整回调函数的刷新时机。pre默认值,在元素 挂载 或者 更新 之前执行; post将会使侦听器延迟到组件渲染之后再执行;sync 在响应式依赖发生改变时立即触发侦听器。该设置应谨慎使用,因为如果有多个属性同时更新,这将导致一些性能和数据一致性的问题。
- onTrack / onTrigger:调试侦听器的依赖
- 监听单个ref对象的时候,不用
.value
- 监听 reactive 定义的响应式数据时,无法正确的获取 oldValue、强制开启了深度监听(deep 配置失效)
- 监听 reactive 定义的响应式对象中的属性时,需要将该属性放在一个函数中返回,作为 watch 函数的第一个参数
<template>
<div>
{{ msg }}<br />
{{ obj.name }}<br />
{{ subject }}<br />
{{ obj.job.salary }}<br />
<button @click="updateMsg">修改msg</button><br />
<button @click="updateName">修改obj的name</button><br />
<button @click="updateSubject">修改subject</button><br />
<button @click="updateSalary">修改obj.job.salary</button><br />
</div>
</template>
<script >
import { reactive, ref, watch, toRefs } from "vue";
export default {
setup() {
const msg = ref("一个消息");
const subject = ref("一个话题");
const obj = reactive({
name: "张三",
age: 22,
job: {
salary: 20,
},
});
function updateMsg() {
msg.value = "一个新消息";
}
function updateName() {
obj.name = "李四";
}
function updateSubject() {
subject.value = "另一个话题";
}
function updateSalary() {
obj.job.salary ++;
}
//监听一个ref对象
watch(msg, function (newVal, oldVal) {
console.log("msg旧值:", oldVal);
console.log("msg新值:", newVal);
});
//监听多个ref对象,新旧值也是数组
watch([msg, subject], (newVal, oldVal) => {
console.log("监听多个属性的方法被调用", newVal, oldVal);
});
//监听对象中的属性,即使是深度监听也不行
watch(
obj,
(newVal, oldVal) => {
console.log("obj 旧值:", oldVal);
console.log("obj 新值:", newVal);
},
{ deep: true }
);
//监听对象中的属性,无法监听,会报一个警告
watch(
obj.name,
(newVal, oldVal) => {
console.log("深度监听obj.name旧值:", oldVal);
console.log("深度监听obj.name新值:", newVal);
},
{ deep: true }
);
//监听对象属性的方法一
watch(
() => obj.name,
(newVal, oldVal) => {
console.log("方法一:obj.name旧值:", oldVal);
console.log("方法一:obj.name新值:", newVal);
}
);
//监听对象属性的方法二
const { name: tempName } = toRefs(obj);
watch(tempName, (newVal, oldVal) => {
console.log("方法二:obj.name旧值:", oldVal);
console.log("方法二:obj.name新值:", newVal);
});
//监听对象的对象属性, 需要开启 deep
watch(() => obj.job, (newVal, oldVal) => {
console.log("job 旧值:", oldVal);
console.log("job 新值:", newVal);
}, {deep: true});
//这时不需要开启 deep
watch(obj.job, (newVal, oldVal) => {
console.log("job 旧值2:", oldVal);
console.log("job 新值2:", newVal);
}, {deep: false});
return { msg, updateMsg, obj, updateName, subject, updateSubject, updateSalary };
},
};
</script>
🌀watchEffect函数
- watchEffect传入的函数会被立即执行一次,并且在执行的过程中会收集依赖
- 只有收集的依赖发生变化时,watchEffect传入的函数才会再次执行;
- 立即执行,没有惰性,页面的首次加载就会执行
- 不需要传递要侦听的内容 会自动感知代码依赖,不需要传递很多参数,只要传递一个回调函数
- 不能获取之前数据的值 只能获取当前值
<template>
<div>
<h2>当前计数: {{ counter }}</h2>
<button @click="counter++">+1</button>
<button @click="name = 'kobe'">修改name</button>
</div>
</template>
<script setup>
import { watchEffect, ref } from 'vue'
const counter = ref(0)
const name = ref("why")
const stopWatch = watchEffect(() => {
console.log("-------", counter.value, name.value)
})
</script>
- watchEffect的停止侦听
<template>
<div>
<h2>当前计数: {{ counter }}</h2>
<button @click="counter++">+1</button>
<button @click="name = 'kobe'">修改name</button>
</div>
</template>
<script setup>
import { watchEffect, ref } from 'vue'
const counter = ref(0)
const name = ref("why")
const stopWatch = watchEffect(() => {
console.log("-------", counter.value, name.value)
//设置条件, stopWatch
if (counter.value >= 10) {
stopWatch()
}
})
</script>
🌀defineProps函数
- 定义props
<script setup>
// 定义props
const props = defineProps({
name: {
type: String,
default: "默认值"
},
age: {
type: Number,
default: 0
}
})
</script>
🌀defineEmits函数
- 绑定函数, 并且发出事件
<script setup>
// 绑定函数, 并且发出事件
const emits = defineEmits(["自定义事件"])
function showInfoBtnClick() {
emits("自定义事件", 参数payload)
}
</script>
🌀defineExpose函数
- 使用
<script setup>
的组件是默认关闭的:- 通过模板 ref 或者 $parent 链获取到的组件的公开实例,不会暴露任何在
<script setup>
中声明的绑定
- 通过模板 ref 或者 $parent 链获取到的组件的公开实例,不会暴露任何在
- 指定组件需要暴露出去的内容
- 父组件ref获取子组件实例,必须暴漏才能访问内容
<script setup>
// 定义foo的函数
function foo() {
console.log("foo function")
}
defineExpose({
foo
})
</script>