文章目录
一、常用Composition API(组合式API)
1、setup
(1)setup是什么
- Vue3.0中一个新的配置项,值为一个函数
- setup是所有Composition API(组合API)
- 组件中所有用到的:数据、方法等等,均要配置在setup中
(2)setup执行的时机
- Vue3.0中一个新的配置项,值为一个函数
- setup是所有Composition API(组合API)
- 组件中所有用到的:数据、方法等等,均要配置在setup中
(3)setup返回值
- 若返回一个对象,则对象中的属性、方法,在模板中均可以直接使用。(常用)
- 若返回一个渲染函数:则可以自定义渲染内容。
import {h} from 'vue'
//向下兼容,可以写入vue2中的data配置项
module default {
name: 'App',
setup(){
//数据
let name = '张三',
let age = 18,
//方法
function sayHello(){
console.log(name)
},
//f返回一个对象(常用)
return {
name,
age,
sayHello
}
//返回一个函数(渲染函数)
//return () => {return h('h1','学习')}
return () => h('h1','学习')
}
}
(4)setup参数props 和 context
- 参数props
//father
<template>
<HelloWorld :message="str" />
</template>
<script>
import { ref } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
},
setup() {
const str = ref('helloword')
return {
str
}
}
}
</script>
//children
<script>
export default {
//必须在这里先定义props
props: {
message: {
type: String,
default: 'nihao'
}
},
setup(props) {
console.log(props.message) // 'helloword'
}
}
</script>
//语法糖写法
<script setup>
const props = defineProps({
foo: {
type: String,
default: '1212312312312312312'
}
})
console.log(props.message) // 'helloword'
}
</script>
- 参数context
context :上下文,包括 attrs 、 emit 、slots
① attrs :在此部分,接收在父组件传递过来的,并且没有在props中声明的参数参数。
② emit:子组件对父组件发送事件
③ slots:和vue2中的插槽使用类似
(5)注意点
- 尽量不要与Vue2.x配置混用
- Vue2.x配置(data,methods,computed…)中可以访问setup中的属性,方法
- 但在setup中不能访问到Vue2.x配置(data,methods,computed…)
- 如有重名,setup优先
- setup不能是一个async函数,因为返回值不再是return的对象,而是Promise,模板看不到return对象中的属性
2、ref函数
- 作用:定义一个响应式的数据
- 语法: const xxx = ref(initValue)
- 创建一个包含响应式数据引用对象
- JS中操作数据:xxx.value
- 模板中读取数据:不需要.value,直接:{{xxx}}
- 备注:
- 接收的数据可以是:基本类型、也可以是对象类型
- 基本类型的数据:响应式依然靠的是Object.defineProperty()的get和set完成的
- 对象类型的数据: 内部”求助“了Vue3.0中的一个新的函数——reactive函数
3、reactive函数
- 作用:定义一个对象类型的响应式数据(基本数据类型别用它,用ref函数)
- 语法:const 代理一个对象 = reactive(被代理的对象) 接收一个对象(或数组),返回一个代理器对象(proxy对象)
- reactive定义的响应式数据是“深层次的”
- 内部基于ES6的Proxy实现,通过代理对象内部的数据都是响应式的
<script setup>
import {reactive} from "vue"
let obj=reactive(
{
name:"aaa",
age:22,
family:[{relation: 'father',age:52}, { relation: 'mother', age: 55 }],
}
)
let fn=()=>{
let fn=()=>{
//直接用点语法,因为reactive使用的是Proxy代理了整个对象
obj.age++
obj.family[0].age++
obj.family[1].age++
}
4、ref函数对比reactive函数
- 从定义数据角度对比:
- ref用来定义: 基本数据类型
- reactive用来定义: 对象(或数组)类型数据
- 备注: ref也可以用来定义对象(或数组)类型数据,它内部会自动通过reactive转为代理对象
- 从原理角度对比:
- ref通过Object.defineProperty()的get和set来实现响应式(数据劫持)
- reactive通过Proxy来实现响应式(数据劫持),并通过Reflect操作源对象内部的数据
- 从使用角度对比:
- ref定义数据:操作数据需要 .value ,读取数据时模板中直接读取不需要 .value
- reactive 定义的数据: 操作数据和读取数据均不需要 .value
5、监听
(1)watch
监视reactive定义的响应式数据的时候:oldValue无法获取到正确的值,强制开启了深度监视(deep配置无效)
<template>
<h1>当前求和为: {{sum}}</h1>
<button @click="sum++">点我+1</button>
<hr>
<h1>当前信息为: {{msg}}</h1>
<button @click="msg+='!' ">修改信息</button>
<hr>
<h2>姓名: {{person.name}}</h2>
<h2>年龄: {{person.age}}</h2>
<button @click="person.name += '~' ">修改姓名</button>
<button @click="person.age++">增长年龄</button>
</template>
<script>
//使用setup的注意事项
import { watch,ref,reactive } from 'vue'
export default {
name: 'test5',
props: ['msg'],
emits:['hello'],
setup(){
let sum = ref(0)
let msg = ref('你好啊')
let person = reactive({
name: '张三',
age: 18,
job:{
salary: '15k'
},
})
//情况一:监视ref所定义的一个响应式数据
watch(sum, (newValue,oldValue)=>{
console.log('新的值',newValue);
console.log('旧的值',oldValue);
})
//情况二:监视ref所定义的多个响应式数据
watch([sum,msg], (newValue,oldValue)=>{
console.log('新的值',newValue); //['sum的newValue', 'msg的newValue']
console.log('旧的值',oldValue); //['sum的oldValue', 'msg的oldValue']
},{immediate: true,deep:true}) //这里vue3的deep是有点小问题的,可以不用deep,(隐式强制deep)
//情况三:监视reactive定义的所有响应式数据,
//1.此处无法获取正确的oldValue(newValue与oldValue是一致值),且目前无法解决
//2.强制开启了深度监视(deep配置无效)
/**
* 备注:
* 1. 当你监听一个响应式对象的时候,这里的newVal和oldVal是一样的,因为他们是同一个对象【引用地址一样】,
* 即使里面的属性值会发生变化,但主体对象引用地址不变。这不是一个bug。要想不一样除非这里把对象都换了
*
* 2. 当你监听一个响应式对象的时候,vue3会隐式的创建一个深层监听,即对象里只要有变化就会被调用。
* 这也解释了你说的deep配置无效,这里是强制的。
*/
watch(person, (newValue,oldValue)=>{
console.log('新的值',newValue);
console.log('旧的值',oldValue);
})
//情况四:监视reactive对象中某一个属性的值,
//注意: 这里监视某一个属性的时候可以监听到oldValue
watch(()=>person.name, (newValue,oldValue)=>{
console.log('新的值',newValue);
console.log('旧的值',oldValue);
})
//情况五:监视reactive对象中某一些属性的值
watch([()=>person.name,()=>person.age], (newValue,oldValue)=>{
console.log('新的值',newValue);
console.log('旧的值',oldValue);
})
//特殊情况: 监视reactive响应式数据中深层次的对象,此时deep的配置奏效了
//注意: 这里监视某一个属性的时候可以监听到oldValue ????
watch(()=>person.job, (newValue,oldValue)=>{
console.log('新的值',newValue);
console.log('旧的值',oldValue);
},{deep:true}) //此时deep有用
return {
sum,
msg,
person,
}
},
}
</script>
(2)watchEffect
备注:
- 不需要指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性
- 页面初始化的时候会执行一次函数,自动收集依赖
- watchEffect有点像computed:
- 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值
- 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值
<template>
<h2>工资: {{person.job.salary}}</h2>
<button @click="person.job.salary++">增长工资</button>
</template>
<script>
import { ref,reactive,watchEffect } from 'vue'
export default {
name: 'test5',
props: ['msg'],
emits:['hello'],
setup(){
let sum = ref(0)
let msg = ref('你好啊')
let person = reactive({
name: '张三',
age: 18,
job:{
salary: 15
},
})
//用处: 如果是比较复杂的业务,发票报销等,那就不许需要去监听其他依赖,只要发生变化,立马重新回调
//注重逻辑过程,你发生改变了我就重新执行回调,不用就不执行,只执行一次
watchEffect(() => {
//这里面你用到了谁就监视谁,里面就发生回调
console.log(person.job.salary, ' 111111');
})
return {
sum,
msg,
person,
}
},
}
</script>
(3)watch 和 watchEffect 区别
- watchEffect不需要制定监听的属性,自动收集依赖,只要在回调中引用到了响应式属性,只要这些属性发生改变,回调就会执行。watch只能监听指定的属性,做出回调函数的执行,同时可以监听多个
- watch可以获取到新值和旧值,watchEffect拿不到旧值
- watchEffect在组件初始化的时候就会自动执行一次用来收集依赖,watch不需要一开始就指定了
6、计算属性computed
(1)定义
computed是vue的计算属性,是根据依赖关系进行缓存的计算,只有在它的相关依赖发生改变时才会进行更新
(2)computed的响应式依赖(缓存)
- computed的每一个计算属性都会被缓存起来,只要计算属性所依赖的属性发生变化,计算属性就会重新执行,视图也会更新。下面代码中,计算属性fullName,它依赖了firstName和lastName这两个属性,只要它们其中一个属性变化,fullName就会重新执行。
- computed计算属性会被缓存,在下面代码中使用了两次fullName,但在控制台只输出了一次 “这是fullName”。
<template>
<div>
<div>
姓:<input type="text" v-model="firstName" />
</div>
<div>
名:<input type="text" v-model="lastName" />
</div>
<!-- 调用两次fullName -->
<div>姓名:{{ fullName }}</div>
<div>姓名:{{ fullName }}</div>
</div>
</template>
<script>
export default {
data() {
return {
firstName: "张",
lastName: "三",
};
},
computed: {
fullName() {
console.log("这是fullName");
return this.firstName + this.lastName;
}
}
};
</script>
(3)vue3使用
import { ref, reactive, computed } from 'vue'
export default {
name: 'HelloWorld',
setup() {
let msg = ref('你好啊')
let person = reactive({
name: '张三',
job: {
salary: 15
},
reverseMsg: computed(() => {
return msg.value.split('').reverse().join('')
})
})
let strings = computed(() => {
//返回一个带有value属性的对象
return msg.value.split('').reverse().join('')
})
console.log(strings.value, person.reverseMsg)
return {
msg,
person,
}
},
}
</script>
(4)应用场景
- 当一个数据受多个数据影响时,可以使用computed
- 本组件计算
- 计算props的值
- 计算vuex的state或者getters值的变化
7、toRefs与toRef
- 作用:创建一个ref对象,其value值指向另一个对象中的某个属性值
- 语法:const name = toRef(person, ‘name’)
- 应用:要将响应式对象中的某个属性单独提供给外部使用
- 扩展: toRefs与toRef功能一致,但是可以批量创建多个ref对象,语法: toRefs(person)
<template>
<h2>姓名: {{name2}}</h2>
<h2>年龄: {{age}}</h2>
<button @click="name += '~' ">修改姓名</button>
<button @click="age++">增长年龄</button>
</template>
<script>
//使用setup的注意事项
import { reactive, toRef, toRefs } from 'vue'
export default {
setup(){
let person = reactive({
name: '张三',
age: 18,
job:{
salary: '15k'
},
})
//toRef
const name2 = toRef(person,'name') //第一个参数是对象,第二个参数是键名
console.log('toRef转变的是',name2); //ref定义的对象
//toRefs,批量处理对象的所有属性
//const x = toRefs(person)
//console.log('toRefs转变的是',x); //是一个对象
return {
person,
name2,
...toRefs(person)
}
},
}
</script>
8、provide与inject
- 作用:实现祖孙组件间的通信
- 语法:父组件有一个provide选项提供数据,子组件有一个inject选项来开始使用这些数据
- 具体写法:
<script setup>
// 父组件
import { provide } from 'vue';
import ChildVue from './components/Child.vue';
let car = reactive({
name: '奔驰',
price: '40w'
})
provide('car',car) //给自己的后代组件传递数据
</script>
<script setup>
// 子组件/孙组件
import { inject } from 'vue';
et car = inject('car') //拿到父组件的数据
const {name, price} = car
</script>
9、shallowReactive 与 shallowRef (不常用)
- shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
- shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。
- 什么时候使用?
- 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
- 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。
- 代码演示
(1)shallowReactive:
<template>
<div>
<h1>姓名:{{name}}</h1> //响应式发生变化
<h2>年龄:{{age}}</h2> //响应式发生变化
<h3>喜欢的水果:{{likeFood.fruits.apple}}</h3> //普通对象不会发生变化
<button @click="name += '~'">修改姓名</button>
<button @click="age++">修改年龄</button>
<button @click="likeFood.fruits.apple += '!'">修改水果</button>
</div>
</template>
<script>
import {reactive, toRefs, shallowReactive} from 'vue'
export default {
name: "App",
setup() {
// 定义了一段数据
let person = shallowReactive({ // 只将第一层数据做了响应式处理
name: '张三',
age: 18,
likeFood: {
fruits:{
apple: '苹果' // 深层次的数据将会是一个普通的对象
}
}
})
// 将数据返回出去
return {
...toRefs(person)
}
}
};
</script>
(2)shallowRef
// 我们调用了shallowRef方法传递了基本数据类型 我们可以看到 当前属性是有响应式的
<div>
<h1>姓名:{{ sum}}</h1> // 响应式数据变化
<button @click="sum++">点击+</button>
</div>
setup() {
// 定义了一段数据
let sum = shallowRef(0);
// 将数据返回出去
return {
sum,
};
},
//但是我们现在传递一个对象进去 我们可以看到 传递的对象将变成一个普通对象 不在具有响应式功能了
<div>
<h1>姓名:{{ sum.n}}</h1> // 数据不会发生变化
<button @click="sum.n++">点击+</button>
</div>
setup() {
// 定义了一段数据
let sum = shallowRef({ // sum将不在是一个响应式对象
n: 0
});
// 将数据返回出去
return {
sum,
};
},
10、readonly与shallowReadonly (不常用)
- readonly:让一个响应式的数据变成只读的(深只读)
- shallowReadonly: 让一个响应式数据变成只读的(浅只读)
<template>
<h4>当前求和为:{{ sum }}</h4>
<button @click="sum++">点我++</button>
<hr>
<h2>姓名:{{ name }}</h2>
<h2>年龄:{{ age }}</h2>
<h2>薪资:{{ job.j1.salary }}K</h2>
<button @click="name += '~'">修改姓名</button>
<button @click="age++">增长年龄</button>
<button @click="job.j1.salary++">涨薪</button>
</template>
<script>
//shallowReadonly
import { ref, reactive, toRefs, readonly, shallowReadonly } from 'vue'
export default {
setup() {
//数据
let sum = ref(0)
let person = reactive({
name: '张三',
age: 18,
job: {
j1: {
salary: 20
}
}
})
person = readonly(person) //全部都不能修改
person = shallowReadonly(person) //salary可以修改
sum = readonly(sum) //不能修改
sum = shallowReadonly(sum) //不能修改
//返回一个对象(常用)
return {
sum,
...toRefs(person)
}
}
}
</script>
11、toRaw与markRaw (不常用)
- toRaw
- 作用:将一个由reactive生成的响应式对象转换为普通对象
- 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新
- markRaw
- 作用:标记一个对象,使其永远不会再成为响应式对象
- 使用场景:
- 有些值不应被设置成响应式的,例如复杂的第三方类库等
- 当渲染具有不可变数据的大列表时候,跳过响应式转换可以提高性能
import {reactive,toRaw,markRaw} from 'vue'
setup(){
let person = reactive({
name: '张三',
})
function showRawPerson(){
const p = toRaw(person)
p.age++
console.log(p)
}
function addCar(){
let car = {name: '奔驰'}
person.car = markRaw(car) //一旦这么做时候,他就永远不能当成响应式数据去做了
}
}
12、响应式数据的判断 (不常用)
- isRef:检查一个值是否为ref对象
- isReactivce:检查一个对象是否是由reactive创建的响应式代理
- isReadonly:检查一个对象是否由readonly创建的只读代理
- isProxy:检查一个对象是否由reactive或者readonly方法创建的代理
<script>
import { ref, reactive, toRefs, readonly, isRef, isReactive, isReadonly, isProxy } from 'vue'
export default {
name: 'App',
setup() {
let car = reactive({ name: '奔驰', price: '40W' })
let sum = ref(0)
let car2 = readonly(car)
console.log(isRef(sum)) //true
console.log(isReactive(car)) //true
console.log(isReadonly(car2)) //true
console.log(isProxy(car)) //true
console.log(isProxy(sum)) //false
return { ...toRefs(car) }
}
}
</script>
二、生命周期钩子
onMounted()
onUpdated()
onUnmounted()
onBeforeMount()
onBeforeUpdate()
onBeforeUnmount()
onActivated()
onDeactivated()
onServerPrefetch()
三、组合式api 父子方法互相调用
(1)子组件调用父组件方法
父组件通过ref定义子组件实例,通过调用实例获得子组件的数据和方法
<!-- 父组件 app.vue -->
<template>
<div class="itxst">
<!-- 使用 ref 指令关联子组件 -->
<child ref="childComp"/>
<button @click="onTry">点击试一试</button>
</div>
</template>
<script setup>
import { reactive, ref } from "vue";
import child from "./child.vue";
//定义子组件实例,名称要和上面的ref相同
const childComp = ref(null);
//访问demo组件的方法或对象
const onTry = () => {
//获取到子组件的 title 数据
let msg = childComp.value.state.title;
//调用子组件的 play方法
childComp.value.play();
};
</script>
子组件通过defineExpose暴露对象和方法
<!--子组件名称 child.vue -->
<template>
<div class="itxst">
{{ state.title }}
</div>
</template>
<script setup>
import { ref, reactive } from "vue";
//定义一个变量
const state = reactive({
title: "www.itxst.com",
});
//定义一个方法
const play = () => {
state.title = "你调用了子组件的方法";
};
//暴露state和play方法
defineExpose({
state,
play,
});
</script>
(2)父组件调用子组件方法
父组件
<template>
<Child @sayHello="handle"></Child>
</template>
<script lang="ts" setup>
import Child from '../../components/child.vue';
const handle = () => {
console.log('子组件调用了父组件的方法')
}
</script>
子组件
<template>
<view>我是子组件</view>
<button @click="say">调用父组件的方法</button>
</template>
<script lang="ts" setup>
const emit = defineEmits(["sayHello"])
const say = () => {
emit('sayHello')
}
</script>
四、Vue3到底做了哪些优化
(1)Vue2存在的问题
- 随着功能的增长,复杂组件的代码变得越来越难以维护;
- 缺少一种比较“干净”的在多个组件之间提取和复用逻辑的机制;
- 类型推断不够友好;
- bundle的时间过久。
(2)Vue3的更小
- Vue3移除一些不常用的API,引入tree-shaking,可以将无用模块“剪辑”,仅打包需要的,使打包的整体体积变小了。简单来说,treeshaking就是找出使用的代码。
- Vue2中,无论使用什么功能,最终都会出现在生产代码中,主要原因是Vue实例在项目是单例的,捆绑程序无法检测到该对象的哪些属性在代码中被使用到。
import Vue from 'vue'
Vue.nextTick(() => {})
Vue3中引入tree shaking特性,将全局API进行分块,如果不使用某些功能,就不会出现在打包文件中。
import { nextTick, observable } from 'vue'
nextTick(() => {})
(3)Vue3的更快
在更快方面,Vue3主要做出的优化有:diff算法优化、静态提升、事件监听缓存、SSR优化。
1、diff算法优化
diff算法详情
vue3在diff算法中相比于vue2增加了静态标记,作用是为会发生变化的地方添加一个flag标记,下次发生变化的时候直接找该地方进行比较。比如下述例子:
<template>
<div id="content">
<p class="text">静态文本</p>
<p class="text">静态文本</p>
<p class="text">{{ message }}</p>
<p class="text">静态文本</p>
...
<p class="text">静态文本</p>
</div>
</template>
这里的组件内部只有一个动态节点,剩余一堆都是静态节点,所以这里很多diff和遍历其实都是不需要的,会造成性能浪费。此时会给{{message}}标签标记一个flag=1,其他的静态文本会标记flag=-1,代表永远都不会进行diff操作。这样比较的时候可以直接选择flag=1的节点进行比较。
2、静态提升
Vue3对于不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用,这样就可以免去了重复的创建节点。
3、事件监听缓存
默认情况下onClick会被视为动态绑定,所以每次都会去追踪它的变化,但是因为是同一个函数,所以没必要去追踪它的变化,想办法将它直接缓存起来复用就会提升性能。因此要打开事件监听缓存,这样静态标记就不存在了,这部分内容也就不会进行比较了。
4、SSR优化
当静态内容达到一定量级的时候,会用createStaticVNode方法在客户端去生成一个static node,这些静态node,会直接innerHtml,就不需要创建对象,然后根据对象渲染。
(4)Vue3更友好
vue3的更友好体现在,兼顾vue2的options API的同时还推出了composition API,大大增加了代码的逻辑组织和代码的复用能力,同时优化了vue2中数据劫持,vue2中通过Object.defineProperty,这个API并不能检测对象属性的添加和删除,而Vue3通过proxy解决了这个问题。
1、 Composition API
- Vue2中,代码是Options API风格的,也就是通过填充(option)data、methods、computed等属性来完成一个Vue组件。这种风格始得Vue容易上手,但是Options API不够灵活,组件间很难优雅的公用代码,并且Vue2的书写方式与JS的开发原则相悖,比如methods中的this竟然指向实例而不指向methods所在的对象。
- 在Vue3中,舍弃了Options API,开始使用Composition API。组件根据逻辑功能来组织的,一个功能所定义的所有 API 会放在一起(更加的高内聚,低耦合)。
2、 proxy
Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象现有属性,并返回此对象。但是,如果存在深层的嵌套对象关系,需要深层次的进行监听,造成了性能的极大问题。
Proxy的监听是针对一个对象的,那么对这个对象的所有操作会进入监听操作,这就完全可以代理所有属性,包括新增属性和删除属性,并且Proxy可以监听数组的变化。
(5)总结:Vue3对于Vue2有什么更新
- Vue2在使用过程中,随着项目越来越复杂,项目代码维护起来也越来越困难,主要原因是Vue2使用的是Options API,这种方式把同一个功能的数据、方法、请求都分开写在data、methods等Options中。并且组件之间相同功能的复用比较也比较困难,同时响应式也没有那么灵活,因此,Vue3做出了如下的更新:
- 用Composition API代理Options API,正如刚刚所说,Options API 将相同的功能的不同部分都分开写,不仅不利于阅读和维护,也和原生JS的思想相悖,缺乏灵活性,Vue3采用的Composition API按照功能将代码分割开,这样方便维护,也方便复用。
- 采用Proxy代理Object.defineProperty,Vue2通过defineProperty的get、set和发布订阅来完成响应式,但是defineProperty的get、set并不能监控深层的对象与数组的变化,需要手动调用set来增加、删除响应式属性,还是造成了一些麻烦。Vue3采用Proxy监控整个对象,无论多深的属性都可以被监控到。
- Vue3增加了tree shaking,能在打包的时候只打包用到的组件,可以让运行速度更快和打包文件更小。
- Vue3还改变了虚拟DOM的diff策略,在Vue2中,diff策略不会区别节点是静态节点还是动态节点,而对比过多的静态节点会造成资源的浪费。因此- - Vue3给每一个节点都打上了标签,如果标签不为-1,则证明是动态节点,在比较的时候也只需要比较动态节点,使diff算法的效率更高。
- Vue3还增加了静态提升和事件监听缓存,将不需要重复创建的节点和方法单独提出、进行缓存,避免重复创建和加载。
Vue3还做了SSR优化。如果增加的静态内容过多,就会直接使用innerHTML的方法插入,而不会一个一个的创建的节点。