一. setup
- 定义
setup是Composition API的入口,是一个函数。
- 执行过程
setup执行过程,在vue生命周期beforeCreate和created之前。
beforeCreate:组件才刚刚创建出来,data和methods还没初始化
created:组件已创建出来,data和methods也已初始化了。
ex:setup–>beforeCreate–>created
- 注意点
- 由于执行setup函数时,还没执行created生命周期的方法。所有在setup函数中无法使用data和methods。
- 由于在setup函数中无法使用data和methods,所以setup函数中无法使用this。this修改成uundefined。
- setup只能是同步函数,不能是异步函数。setup前面不能写async 。
- 不要与Vue2配置混用,Vue2配置能在setup访问,但Vue3不能访问Vue2的配置
- 如果Vue2和Vue3混用,而且都定义重名的变量与方法,Vue3优先级大于Vue2。
- setup参数
- props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
- 因为 props 是响应式的,你不能使用 ES6 解构,它会消除 prop 的响应性。
- 如果需要解构 prop,可以在 setup 函数中使用 toRefs 函数来完成此操作:
import { toRefs } from 'vue'
a:{
type:String,
default:"哈哈哈"
},
b:{
type:Number,
default:0
}
setup(props) {
const { a } = toRefs(props)
console.log(11, a.value);
console.log(11, props.b);
}
- context:上下文对象
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
this.$attrs
。 - slots: 收到的插槽内容, 相当于
this.$slots
。 - emit: 分发自定义事件的函数, 相当于
this.$emit
。 - expose:暴露公共 property (函数)
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
context 是一个普通的 JavaScript 对象,也就是说,它不是响应式的,这意味着你可以安全地对 context 使用 ES6 解构。
export default {
setup(props, { attrs, slots, emit, expose }) {
...
}
}
二. ref
- 定义
ref和reactive一样,也是用来实现响应式数据的方法-由于reactive必须传递一个对象,所以导致在企业开发中如果我们只想让某个变量实现响应式的时候会非常麻烦所以Vue3就给我们提供了ref方法,实现对简单值的监听。
- ref底层的本质其实还是reactive
- 系统会自动根据我们给ref传入的值将它转换成ref(xx) -> reactive({value : xx})
ex: let age = ref(xxx) ->reactive(age:{vaule:xxx})
- 注意点
let age = ref(xxx)
- 在Vue中使用ref的值不用通过value获取,在template 中{{age }}就行了
- 在JS中使用ref的值必须通过value获取。在function函数中使用 age.value =xxx

<template>
<div class="ho">
<div>{{a}}</div>
<el-button @click="cilck1">点击</el-button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup () {
const a = ref(2)
function cilck1 () {
a.value += 2
}
return { cilck1, a }
}
}
</script>
<style lang="scss" scoped>
</style>
三. reactive
- 定义
reactive是vue3中提供响应式数据的方法,其语法糖是通过ES6 **proxy **来实现响应式数据的。(vue2是通过 **defineProperty **来实现响应式数据的。)
- 注意点
- reactive参数里必须是对象(object)或者数组(arr)
- reactive参数存在其他对象new,ex:new Date(),想要修改里面的值,必须重新赋值,才具有响应式。
四. vue3 响应原理
vue2.x的响应式
- 实现原理:
- 对象类型:通过
Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持)。 - 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
- 对象类型:通过
‘push’, ‘pop’, ‘shift’, ‘unshift’, ‘splice’, ‘sort’, ‘reverse’
Object.defineProperty(data, 'count', {
get () {},
set () {}
})
- 存在问题:
- 新增属性、删除属性, 界面不会更新。
Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。
对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式 property。
- 直接通过下标修改数组, 界面不会自动更新。
Vue3.0的响应式
- 实现原理:
- 通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
- 通过Reflect(反射): 对源对象的属性进行操作。
- MDN文档中描述的Proxy与Reflect:
new 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'
五.reactive对比ref
- 从定义数据角度对比:
- ref用来定义:基本类型数据。
- reactive用来定义:对象(或数组)类型数据。
- 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过
reactive
转为代理对象。
- 从使用角度对比:
- ref定义的数据:操作数据需要
.value
,读取数据时模板中直接读取不需要.value
。 - reactive定义的数据:操作数据与读取数据:均不需要
.value
- ref定义的数据:操作数据需要
六. 计算属性和监听器
计算属性(computed)
- 计算属性默认第一次加载的时候就开始监听所依赖的属性,所依赖的属性没有发生变化,那么调用当前的函数的时候会从缓存中读取
- 计算属性的函数中如果只传入一个回调函数,表示get
- 计算属性返回的是一个ref对象,具有响应式
- 计算属性的函数可以有get()和set()
<template>
<div class="home">
<div style="width: 100px">
价格:<el-input v-model="goods.price"></el-input>
数量:<el-input v-model="goods.num"></el-input>
{{ goods.sum }}
</div>
<el-button type="primary">点击</el-button>
</div>
</template>
<script>
import { reactive, toRefs, computed } from "vue";
export default {
setup() {
let data = reactive({
goods: {
price: 0,
num: 0,
// 方式一 ,用于计算结果不改变场景
// sum: computed(() => data.goods.price * data.goods.num),
//方式二 ,用于计算结果改变场景
sum: computed({
//返回计算结果
get() {
return data.goods.price * data.goods.num;
},
set(val) {},
}),
},
});
return {
...toRefs(data),
};
},
};
</script>
监听器(watch)
- ref
- 监听单项数据
语法:
let strVal = ref(0)
watch(strVal,(newValue,oldValue)=>{
console.log(‘strVal变化了’,newValue,oldValue)
},{immediate:false,deep:false})strVal:变量
immediate:在选项参数中指定 immediate: true 将立即以表达式的当前值触发回调:默认值为false
**deep:**为了发现对象内部值的变化,可以在选项参数中指定 deep: true。这个选项同样适用于监听数组变更。
<template>
<div>
{{ studentNum }}
<el-button type="primary" @click="studentNum++">点击增加</el-button>
</div>
</template>
<script>
import { ref, watch } from "vue";
export default {
setup() {
let studentNum = ref(0);
watch(studentNum, (newVal, oldVal) => {
console.log("新值", newVal);
console.log("旧值", oldVal);
});
return {
studentNum,
};
},
};
</script>
- 监听多项
语法:
let strVal = ref(0)
let aVal = ref(“0”)
watch([strVal,aVal],(newValue,oldValue)=>{
console.log(‘strVal变化了’,newValue,oldValue)
//newValue,oldValue 是数组,通过数组下标取值
},{immediate:false,deep:false})
<!-- -->
<template>
<div>
{{ studentNum }}
{{ teacherNum }}
<el-button type="primary" @click="studentNum++,teacherNum++"
>点击增加</el-button
>
</div>
</template>
<script>
import { ref, watch } from "vue";
export default {
setup() {
let studentNum = ref(0);
let teacherNum = ref(0);
watch([studentNum, teacherNum], (newVal, oldVal) => {
console.log("新值", newVal);
console.log("旧值", oldVal);
});
return {
studentNum,
teacherNum,
};
},
};
</script>
- reactive
- 监视reactive定义的响应式数据
语法:
let data = reactive({
xx:0,
ss:0,
});
watch(data, (newVal, oldVal) => {
console.log(“新值”, newVal);
console.log(“旧值”, oldVal);
},{immediate:false,deep:false});
//此处的deep配置不再奏效,永远true
data:对象
immediate:在选项参数中指定 immediate: true 将立即以表达式的当前值触发回调:默认值为false
**deep:**为了发现对象内部值的变化,可以在选项参数中指定 deep: true。这个选项同样适用于监听数组变更。
注意:
若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue,newValue值和oldValue值相同
若watch监视的是reactive定义的响应式数据,则**强制开启了深度监视(deep:true) **
<!-- -->
<template>
<div>
<div>
{{ studentNum }}
{{ teacherNum }}
{{ schoolArr }}
</div>
<el-button type="primary" @click="handleClick">点击增加</el-button>
</div>
</template>
<script>
import { watch, reactive, toRefs } from "vue";
export default {
setup() {
let data = reactive({
studentNum: 0,
teacherNum: 0,
schoolArr: ["海南大学", "北京大学", "东京大学", "西湖大学"],
});
watch(data, (newVal, oldVal) => {
console.log("新值", newVal);
console.log("旧值", oldVal);
});
function handleClick() {
data.studentNum++;
data.teacherNum++;
data.schoolArr[0] = "四川大学";
}
return {
...toRefs(data),
handleClick,
};
},
};
</script>
- 监视reactive定义的响应式数据中的某个属性
使用侦听器来比较一个数组或对象的值,这些值是响应式的,要求它有一个由值构成的副本。
注释:因为数组和对象是引用类型,如果没有副本 old 和 new 会是同一个值
语法:
let data = reactive({
xx:0,
ss:0,
});
watch(()=>data.xx, (newVal, oldVal) => {
console.log(“新值”, newVal);
console.log(“旧值”, oldVal);
},{immediate:false,deep:false});
()=>data.xx:对象中某个属性,是个响应式副本
immediate:在选项参数中指定 immediate: true 将立即以表达式的当前值触发回调:默认值为false
**deep:**为了发现对象内部值的变化,可以在选项参数中指定 deep: true。这个选项同样适用于监听数组变更。
<!-- -->
<template>
<div>
<div>
{{ studentNum }}
{{ teacherNum }}
{{ schoolArr }}
</div>
<el-button type="primary" @click="handleClick">点击增加</el-button>
</div>
</template>
<script>
import { watch, reactive, toRefs } from "vue";
export default {
setup() {
let data = reactive({
studentNum: 0,
teacherNum: 0,
schoolArr: ["海南大学", "北京大学", "东京大学", "西湖大学"],
});
watch(()=>data.studentNum, (newVal, oldVal) => {
console.log("新值", newVal);
console.log("旧值", oldVal);
});
function handleClick() {
data.studentNum++;
data.teacherNum++;
data.schoolArr[0] = "四川大学";
}
return {
...toRefs(data),
handleClick,
};
},
};
</script>
- 监视reactive定义的响应式数据中的某些属性
使用侦听器来比较一个数组或对象的值,这些值是响应式的,要求它有一个由值构成的副本。
注释:因为数组和对象是引用类型,如果没有副本 old 和 new 会是同一个值
语法:
let data = reactive({
xx:0,
ss:0,
});
watch([()=>data.xx,()=>data.ss], (newVal, oldVal) => {
console.log(“新值”, newVal); //[]
console.log(“旧值”, oldVal);//[]
},{immediate:false,deep:false});
()=>data.xx,()=>data.ss:对象中某个属性,副本
immediate:在选项参数中指定 immediate: true 将立即以表达式的当前值触发回调:默认值为false
**deep:**为了发现对象内部值的变化,可以在选项参数中指定 deep: true。这个选项同样适用于监听数组变更。
<!-- -->
<template>
<div>
<div>
{{ studentNum }}
{{ teacherNum }}
{{ schoolArr }}
</div>
<el-button type="primary" @click="handleClick">点击增加</el-button>
</div>
</template>
<script>
import { watch, reactive, toRefs } from "vue";
export default {
setup() {
let data = reactive({
studentNum: 0,
teacherNum: 0,
schoolArr: ["海南大学", "北京大学", "东京大学", "西湖大学"],
});
watch(
[() => data.studentNum, () => data.teacherNum, () => [...data.schoolArr]],
(newVal, oldVal) => {
console.log("新值", newVal);
console.log("旧值", oldVal);
},
{
deep: true,
}
);
function handleClick() {
data.studentNum++;
data.teacherNum++;
data.schoolArr[0] = "四川大学";
}
return {
...toRefs(data),
handleClick,
};
},
};
</script>
- 监视reactive定义的响应式数据中的对象里某个属性
语法:
let data = reactive({
xx:{
ss:0
}
});
watch(()=>data.xx, (newVal, oldVal) => {
console.log(“新值”, newVal);
console.log(“旧值”, oldVal);
},{immediate:false,deep:false});
()=>data.xx:对象
immediate:在选项参数中指定 immediate: true 将立即以表达式的当前值触发回调:默认值为false
**deep:**为了发现对象内部值的变化,可以在选项参数中指定 deep: true。这个选项同样适用于监听数组变更。
由于监视的是reactive元素定义的对象中的某个属性,所以deep:true配置才能监视,反之,无效。
这个对象是响应式数据,newValue值和oldValue值相同
<!-- -->
<template>
<div>
<div>
{{ obj }}
</div>
<el-button type="primary" @click="handleClick">点击增加</el-button>
</div>
</template>
<script>
import { watch, reactive, toRefs } from "vue";
export default {
setup() {
let data = reactive({
studentNum: 0,
teacherNum: 0,
schoolArr: ["海南大学", "北京大学", "东京大学", "西湖大学"],
obj: {
a: 1,
b: 2,
c: 3,
},
});
watch(
() => data.obj,
(newVal, oldVal) => {
console.log("新值", newVal);
console.log("旧值", oldVal);
},
{
deep: true,
}
);
function handleClick() {
data.obj["a"] = "0";
}
return {
...toRefs(data),
handleClick,
};
},
};
</script>
七. watchEffect
传入一个副作用函数并执行,追踪函数内依赖的变量,当依赖的变量发生变化时再次执行副作用函数函数。(类似computed)
副作用(side effect)就是如果有一个函数在输入和输出之外还做了其他的事情,那么这个函数额外做的事情就被称为副作用。
vue3中的副作用,响应式数据的变更造成的其他连锁反应,以及后续逻辑,这些连锁反应都叫副作用。watchEffect函数的onInvalidate方法就是用来清除副作用的,但副作用不一定是不被需要的。它可以是获取数据、事件监听或订阅、改变应用状态、修改 DOM、输出日志等等。清除副作用实际上是Vue3提供给用户的一种取消异步副作用的实现方法。
基本语法
watchEffect(()=>{})
<!-- -->
<template>
<div>
<p>{{ num }}</p>
</div>
</template>
<script>
import { reactive, toRefs, watchEffect } from "vue";
export default {
setup() {
const data = reactive({
num: 0,
});
watchEffect(() => {
console.log(data.num);
});
setTimeout(() => {
data.num++;
}, 1000);
clearTimeout(() => {}, 1000);
return {
...toRefs(data)
};
},
};
</script>
结果会打印出0和1,0 是在响应式元素(num)依赖收集阶段会运行一次effect函数;1是来自setTimeout里对num加一操作。
- 停止监听
watchEffect会返回一个停止侦听的函数,执行次函数就可以停止侦听依赖变量的变化。
当 watchEffect 在组件的** setup() 函数或生命周期钩子**被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。
语法:
const stop = watchEffect(()=>{})
<template>
<div>
<p>{{ num }}</p>
<el-button @click="handle1">阻止WatchEffect</el-button>
</div>
</template>
<script>
import { reactive, toRefs, watchEffect } from "vue";
export default {
setup() {
const data = reactive({
num: 0,
});
const stop = watchEffect(() => {
console.log(data.num);
});
let clear = setInterval(() => {
data.num++;
}, 1000);
let handle1 = () => {
stop();
clearInterval(clear)
};
return {
...toRefs(data),
handle1,
};
},
};
</script>
- 清除副作用
有时副作用函数会执行一些异步的副作用,这些响应需要在其失效时清除 (即完成之前状态已改变了) 。所以侦听副作用传入的函数可以接收一个 onInvalidate 函数作入参,onInvalidate只作用于异步函数,用来注册清理失效时的回调。当以下情况发生时,这个失效回,调会被触发:
- 副作用即将重新执行时(依赖的变量发生变化时,第二次执行watchEffect函数,先执行onInvalidate回调函数,在执行watchEffect里面的函数)
- 侦听器被停止 (如果在 setup() 或生命周期钩子函数中使用了 watchEffect,则在组件卸载时)(就是切换组件)
语法:
watchEffect( (onInvalidate) => {
onInvalidate(() => { }) })
const state = reactive({
title: "1",
});
watchEffect((onInvalidate) => {
onInvalidate(() => {
console.log(111);
});
console.log(state.title);
});
当组件初次渲染时,watchEffect函数会执行一次,追踪函数内依赖的变量,结果会输出 ‘1’ 。
当依赖变量变化时,title改变’2’,再次触发watchEffect函数,则先回执行onInvalidate函数,然后在执行watchEffect里面的函数,结果输出 111, 2
- 配置flush
通过watchEffect的第二个参数配置,副作用函数的执行刷新时机
语法:
watchEffect(() => {},
{
flush: 'post'|'pre'|'sync'//默认值为 'pre'
}
)
-
post:组件渲染完成后执行副作用函数
-
pre: 组件渲染之前执行副作用函数
-
sync:立即执行副作用函数
-
副作用刷新时机
Vue 的响应性系统会缓存副作用函数,并异步地刷新它们,这样可以避免同一个“tick” 中多个状态改变导致的不必要的重复调用。
在核心的具体实现中,组件的update函数也是一个被侦听的副作用。当一个用户定义(watchEffect)的副作用函数进入队列时,默认情况下,会在所有的组件update前执行。
所谓组件的update函数是Vue内置的用来更新DOM的函数,是最终把vnode节点渲染成真实dom的函数。它会在首次渲染和数据更新的时候被调用,它也是副作用,默认情况下,Vue会先执行监听器。
<!-- -->
<template>
<div>
<p id="reFs">{{ num }}-{{ price }}</p>
<el-button @click="num++, price++">++</el-button>
</div>
</template>
<script>
import { reactive, toRefs, watchEffect } from "vue";
export default {
setup() {
const data = reactive({
price: 1,
num: 0,
});
//当进入组件时,watchEffect执行,而后在更新DOM
watchEffect(
() => {
console.log("监听num", data.num, data.price);
console.log(
document.querySelector("#reFs") &&
document.querySelector("#reFs").innerText
);//null
},
{ flush: "pre" }
);
return {
...toRefs(data),
};
},
};
</script>
进入组件执行时,你会发现,document.querySelector(“#reFs”)第一次为null,随后多次点击按钮,document.querySelector(“#reFs”)获取到的总是点击之前DOM的内容。这也说明,默认Vue先执行监听器,所以取到了上一次的内容,然后执行组件update。
如果要获取组件更新完成之后的DOM,就要配置上述的flush为post。
<!-- -->
<template>
<div>
<p id="reFs">{{ num }}-{{ price }}</p>
<el-button @click="num++, price++">++</el-button>
</div>
</template>
<script>
import { reactive, toRefs, watchEffect } from "vue";
export default {
setup() {
const data = reactive({
price: 1,
num: 0,
});
//当进入组件时,watchEffect执行,而后在更新DOM
watchEffect(
() => {
console.log("监听num", data.num, data.price);
console.log(
document.querySelector("#reFs") &&
document.querySelector("#reFs").innerText
);//输出为 0-1
},
{ flush: "post" }
);
return {
...toRefs(data),
};
},
};
</script>
从 Vue 3.2.0 开始,watchPostEffect 和 watchSyncEffect 别名也可以用来让代码意图更加明显。
八. 生命周期
- 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
errorCaptured
=====>onErrorCaptured
activated
=====>onActivated
deactivated
=====>onDeactivated
<!-- -->
<template>
<div>
{{ num }}
<el-button @click="num++">点击更新</el-button>
</div>
</template>
<script>
import {
ref,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
} from "vue";
export default {
beforeCreate() {
console.log("配置项beforeCreate");
},
created() {
console.log("配置项created");
},
beforeMount() {
console.log("配置项beforeMount");
},
mounted() {
console.log("配置项mounted");
},
beforeUpdate() {
console.log("配置项beforeUpdate");
},
updated() {
console.log("配置项updated");
},
beforeUnmount() {
console.log("配置项beforeUnmount");
},
unmounted() {
console.log("配置项unmounted");
},
setup() {
let num = ref(0);
console.log("script配置setup");
onBeforeMount(() => {
console.log("script配置onBeforeMount");
});
onMounted(() => {
console.log("script配置onMounted");
});
onBeforeUpdate(() => {
console.log("script配置onBeforeUpdate");
});
onUpdated(() => {
console.log("script配置onUpdated");
});
onBeforeUnmount(() => {
console.log("script配置onBeforeUnmount");
});
onUnmounted(() => {
console.log("script配置onUnmounted");
});
return {
num,
};
},
};
</script>
输出
script配置setup
配置项beforeCreate
配置项created
script配置onBeforeMount
配置项beforeMount
script配置onMounted
配置项mounted
script配置onBeforeUpdate
配置项beforeUpdate
script配置onUpdated
配置项updated
script配置onBeforeUnmount
配置项beforeUnmount
script配置onUnmounted
配置项unmounted
根据输出数据,setup里的生命周期执行优先级比配置项的生命周期高。
建议在setup使用,使用生命周期
九. 自定义hook函数
- 什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。
- 类似于vue2.x中的mixin。
- 自定义hook的优势:很清楚复用功能代码的来源, 让setup中的逻辑更清楚易懂。
十. toRef与toRefs
- toRef
可以用来为源响应式对象上的某个属性新创建一个 ref。然后,ref 可以被传递,它会保持对其源属性的响应式连接。
语法:
let data = reactive({x:“”,y:“”})
let xx = toRef(data,“x”)
<!-- -->
<template>
<div>
<el-input v-model="txt">11</el-input>
<div>
{{ txt }}
</div>
</div>
</template>
<script>
import { reactive, toRef } from "vue";
export default {
setup() {
let data = reactive({
txt: "",
str: "222",
});
let txt = toRef(data, "txt");
return {
txt,
};
},
};
</script>
- toRefs
可以用来为源响应式对象上的每个属性新创建一个 ref。然后,ref 可以被传递,它会保持对其源属性的响应式连接。
<!-- -->
<template>
<div>
<el-input v-model="txt">11</el-input>
<div>
{{ txt }}
</div>
<el-button @click="handleData">查看reactive</el-button>
</div>
</template>
<script>
import { reactive, toRefs } from "vue";
export default {
setup() {
let data = reactive({
txt: "",
str: "222",
});
let handleData=()=>{
let a = toRefs(data)
console.log(a);
console.log(data);
}
return {
...toRefs(data),
handleData
};
},
};
</script>
十一. shallowReactive 与 shallowRef
- shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
- shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。
- 什么时候使用?
- 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
- 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。
十二. readonly 与 shallowReadonly
- readonly: 让一个响应式数据变为只读的(深只读)。
- shallowReadonly:让一个响应式数据变为只读的(浅只读)。
- 应用场景: 不希望数据被修改时。
十三. toRaw 与 markRaw
- toRaw:
- 作用:将一个由
reactive
生成的响应式对象转为普通对象。 - 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
- 作用:将一个由
- markRaw:
- 作用:标记一个对象,使其永远不会再成为响应式对象。
- 应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类库等。
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
十四. customRef
创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制
- 定义输入框防抖响应式变量
<!-- -->
<template>
<div>
{{txt}}
<el-input v-model="txt"></el-input>
</div>
</template>
<script>
import { customRef } from "vue";
export default {
setup() {
function debounceRef(value, delay) {
let timer;
return customRef((track, trigger) => {
return {
get() {
track();
return value;
},
set(newValue) {
clearTimeout(timer);
timer = setTimeout(() => {
value = newValue;
console.log(newValue);
trigger();
}, delay);
},
};
});
}
let txt = debounceRef(0,100);
return {
txt,
};
},
};
</script>
十五. provide 与 inject
作用:实现祖与后代组件间通信
-
父组件有一个 provide 选项来提供数据,后代组件有一个 inject 选项来开始使用这些数据
-
祖组件
setup(){
......
let data = reactive({xx:'1',yy:'2'})
provide('data',data)
......
}
- 后代
setup(props,context){
......
const data = inject('data')
return {data}
......
}
十六. 响应式数据的判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive
创建的响应式代理 - isReadonly: 检查一个对象是否是由
readonly
创建的只读代理 - isProxy: 检查一个对象是否是由
reactive
或者readonly
方法创建的代理
十七. Fragment
- 在Vue2中: 组件必须有一个根标签
- 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
- 好处: 减少标签层级, 减小内存占用
十八. Teleport
什么是Teleport?—— Teleport 是一种能够将我们的组件html结构移动到指定位置的技术。
Teleport 是一种能够将我们的模板渲染至指定DOM节点,不受父级style、v-show等属性影响,但data、prop数据依旧能够共用的技术;
语法:
- 把弹框放在与body同级
<teleport to="body">
<div v-if="isShow" class="mask">
<div class="dialog">
<h3>我是一个弹窗</h3>
<button @click="isShow = false">关闭弹窗</button>
</div>
</div>
</teleport>
- 自定义与body同级的标签(class id等 选择器),弹框放在此标签上
<teleport to="#xxx">
<div v-if="isShow" class="mask">
<div class="dialog">
<h3>我是一个弹窗</h3>
<button @click="isShow = false">关闭弹窗</button>
</div>
</div>
</teleport>
十九. 动态组件&异步组件
动态组件,使用 异步加载方式 来 加载异步组件;
- 动态组件
动态组件就是 component组件 ,组件身上可以绑定一个is属性, 用来表示某一个组件。
通过使用保留的元素,动态地绑定到它的 is 特性,我们让多个组件可以使用同一个挂载点,并动态切换。根据 v-bind:is=“组件名” 中的组件名去自动匹配组件,如果匹配不到则不显示。
动态组件一般要包裹keep-alive一起使用,缓存组件。
语法:
or
xxx:组件名
- 异步组件
在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。(按需引用组件)
语法:
components: {
AsyncComName:defineAsyncComponent(() => import(“componenUR”)),
},
AsyncComName:异步组件名
componenURL:组件路径
ex:
- home组件
<!-- -->
<template>
<template v-for="(item, index) in componentArr" :key="index">
<el-button
style="margin-right: 10px"
@click="handleCutComponet(item.componentName)"
>{{ item.name }}</el-button
>
</template>
<keep-alive>
<component :is="component" :msg="msg"></component>
</keep-alive>
</template>
<script>
import {
reactive,
markRaw,
toRefs,
defineAsyncComponent,
} from "vue";
//组件引用方式一
// import Table from "@/components/table.vue";
// import Form from "@/components/form.vue";
export default {
components: {
//组件引用方式二 建议使用
Table:defineAsyncComponent(() => import("@/components/table.vue")),
Form:defineAsyncComponent(() => import("@/components/form.vue")),
},
setup() {
//为什么会使用markRaw()?
//因为组件引用也是代理,如果还在reactive定义,那就双重定义proxy,多此一举
const state = reactive({
msg: "hahah",
component:"Table",
componentArr: [
{
name: "表格组件",
componentName: "Table",
},
{
name: "表单组件",
componentName:"Form",
},
],
});
//methods
let handleCutComponet = (componentName) => {
state.component = componentName;
};
return {
...toRefs(state),
handleCutComponet,
};
},
};
</script>
- Table组件
<!-- -->
<template>
<div>表格</div>
<el-input v-model="taVal" />
</template>
<script>
import { reactive, toRefs, onBeforeMount } from "vue";
export default {
props: {
msg: {
type: String,
default: "",
},
},
setup(props) {
console.log("表单", props);
const state = reactive({
taVal: "",
});
return {
...toRefs(state),
};
},
};
</script>
- Form组件
<!-- -->
<template>
<div>表单</div>
</template>
<script>
import { reactive, toRefs, onBeforeMount } from "vue";
export default {
props: {
msg: {
type: String,
default: "",
},
},
setup(props, context) {
console.log("表单", props);
console.log("表单", context);
const state = reactive({});
onBeforeMount(() => {
console.log("2.组件挂载页面之前执行----onBeforeMount");
});
return {
...toRefs(state),
};
},
};
</script>
<style lang='scss' scoped></style>
二十. 插槽
插槽就是子组件中的提供给父组件使用的一个占位符,用 表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的标签。
在Vue3中,用template标签包裹要填充的内容,v-slot属性也需定义在template标签上,只有一种例外情况,就是默认插槽 v-slot:的语法糖是#,但后面必须有插槽名,例如v-slot:default 等价于 #default
插槽的实现原理(/过程):
当子组件vm实例化时,获取到父组件传入的slot标签的内容,存放在vm. s l o t 中,默认插槽为 v m . slot中,默认插槽 为vm. slot中,默认插槽为vm.slot.default,具名插槽为vm. s l o t . x x x , x x x 为插槽名,当组件执行渲染函数时候,遇到 s l o t 标签,使用 slot.xxx,xxx 为插槽名,当组件执行渲染函数时候, 遇到slot标签,使用 slot.xxx,xxx为插槽名,当组件执行渲染函数时候,遇到slot标签,使用slot中的内容进行替换,此时可以为插槽传递数据,若存在数据, 则可称该插槽为作用域插槽
- 匿名插槽
<!-- 子组件 -->
<template>
<div>
<slot></slot>
</div>
</template>
<script>
import { reactive, toRefs } from "vue";
export default {
setup() {
const state = reactive({});
return {
...toRefs(state),
};
},
};
</script>
<style lang='scss' scoped></style>
-------------------------
<!-- 父组件 -->
<template>
<h2>子组件引用</h2>
<Solt>
<p>子组件插槽</p>
</Solt>
</template>
<script>
import { reactive, toRefs } from "vue";
import Solt from "@/components/solt.vue";
export default {
components: {
Solt,
},
setup() {
const state = reactive({});
return {
...toRefs(state),
};
},
};
</script>
<style lang='scss' scoped></style>
- 具名插槽
<!-- 子组件 -->
<template>
<div>
<slot name="header"></slot>
<slot name="main"></slot>
<slot name="footer"></slot>
</div>
</template>
<script>
import { reactive, toRefs } from "vue";
export default {
setup() {
const state = reactive({});
return {
...toRefs(state),
};
},
};
</script>
<style lang='scss' scoped></style>
--------------------------------------------------------
<!-- 父组件 -->
<template>
<h2>具名插槽</h2>
<Solt>
<template #header>
<div>头部</div>
</template>
<template #main>
<div>内容</div>
</template>
<template #footer>
<div>尾部</div>
</template>
</Solt>
</template>
<script>
import { reactive, toRefs } from "vue";
import Solt from "@/components/solt.vue";
export default {
components: {
Solt,
},
setup() {
const state = reactive({});
return {
...toRefs(state),
};
},
};
</script>
<style lang='scss' scoped></style>
- 作用域插槽
作用域插槽的作用即延展子模板中数据的作用域,使得在父级模板中也可使用子模板中的数据
作用域插槽不能用简写
<!-- 子组件 -->
<template>
<div>
<slot name="header" :slotProps="slotProps"></slot>
<slot :content="content"></slot>
<slot name="footer" :slotProps="slotProps"></slot>
</div>
</template>
<script>
import { reactive, toRefs } from "vue";
export default {
props: {
alter: {
type: Number,
default: 0,
},
},
setup(props) {
console.log(props);
const state = reactive({
slotProps: 999,
content: "中间内容",
});
//通过单向数据流修改值
state.slotProps = props.alter;
return {
...toRefs(state),
};
},
};
</script>
<style lang='scss' scoped></style>
------------------------------------------------
<!-- 父组件 -->
<template>
<h2>作用域插槽</h2>
<Solt :alter="alter">
<template v-slot:header="slotProps">
<!-- 访问子组件中“header”插槽的slotProps属性值 -->
<div>头部</div>
<div>{{ slotProps.slotProps }}</div>
</template>
<template v-slot:default="content">
<!-- 访问子组件中对应默认插槽的content属性值 -->
<div>内容</div>
<div>{{ content.content }}</div>
</template>
<template v-slot:footer="{ slotProps: slotProps }">
<div>尾部</div>
<div>{{ slotProps }}</div>
<!--访问子组件中“footer”插槽的slotProps属性值 采用解构赋值的方式 -->
</template>
</Solt>
</template>
<script>
import { reactive, toRefs } from "vue";
import Solt from "@/components/solt.vue";
export default {
components: {
Solt,
},
setup() {
const state = reactive({
alter: 6666,
});
return {
...toRefs(state),
};
},
};
</script>
<style lang='scss' scoped></style>
二十一. 组件传值
父传子
- 父组件的 setup 函数里面定义响应式数据 xxx,并 return 出来
- 父组件的模板中通过 :xxx=“xxx” 传递变量给子组件
- 子组件 通过 props 接收,可以直接在子组件模板中使用
- 子组件从父组件获取的值是只读的,不能直接修改
<!-- 父组件 -->
<template>
<div></div>
<Children :data="data" :str="str" :num="num" />
</template>
<script>
import { reactive, ref, toRefs } from "vue";
import Children from "@/components/children.vue";
export default {
components: {
Children,
},
setup() {
const str = ref("父组件ref传过来的字符值");
const state = reactive({
num: 12138,
data: {
id: 1,
content: "父组件传过来的对象值",
},
});
return {
...toRefs(state),
str,
};
},
};
</script>
-------------------------------------------
<!-- 子组件 -->
<template>
<div>{{ data.content }}</div>
<div>{{ num }}</div>
<div>{{ str }}</div>
</template>
<script>
import { reactive, toRefs } from "vue";
export default {
//方式一 数组形式
// props: ["data","str"],
//方式二 对象形式 (建议)
props: {
data: {
type: Object,
default: () => ({}),
},
str: {
type: String,
default: "",
},
num: {
type: Number,
default: 0,
},
},
setup(props) {
//props 值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
// console.log(props);
//对象
console.log(props.data.content);
//字符串
console.log(props.num);
//数字
console.log(props.str);
return { };
},
};
props(对象)默认值:
demoString: {
type: String,
default: ''
},
demoNumber: {
type: Number,
default: 0
},
demoBoolean: {
type: Boolean,
default: true
},
demoArray: {
type: Array,
default: () => []
},
demoObject: {
type: Object,
default: () => ({})
},
demoFunction: {
type: Function,
default: function () { }
}
子传父
- 父组件绑定自定义事件 :@自定义事件名=“事件处理函数”
- 子组件在 setup 函数中,在特定时间通过 context.emit(‘自定义事件名’,实参) 触发父组件的自定义函数
- 父组件收到子组件传递过来的信号后,进行相应处理
<!-- 父组件 -->
<template>
<div>{{ num }}</div>
<Children @requstNum="requstNum" />
</template>
<script>
import { reactive, toRefs } from "vue";
import Children from "@/components/children.vue";
export default {
components: {
Children,
},
setup() {
const state = reactive({
num: 0,
});
//接收子组件的参数进行赋值
let requstNum = (value) => {
state.num = value;
};
return {
...toRefs(state),
requstNum,
};
},
};
</script>
-------------------------------------------------------
<!-- 子组件 -->
<template>
<el-button type="primary" @click="handleClick">传个父组件的值</el-button>
</template>
<script>
export default {
setup(props, { emit }) {
//context 是一个普通的 JavaScript 对象,也就是说,它不是响应式的,可以使用 ES6 解构。
//触发事件
let handleClick = () => {
emit("requstNum",12138)
};
return { handleClick };
},
};
</script>
二十二. keep-alive(缓存组件)
在平常开发中,有部分组件没有必要多次初始化,这时,我们需要将组件进行持久化,使组件的状态维持不变,在下一次展示时,也不会进行重新初始化组件。
也就是说,kee-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染 。
- 语法
//被keep-alive包含的组件会被缓存
<!-- 基本 -->
<keep-alive>
<component :is="xxx"></component>
</keep-alive>
<!-- 多个条件判断的子组件 -->
<keep-alive>
<comp-a v-if="a > 1"></comp-a>
<comp-b v-else></comp-b>
</keep-alive>
<!-- 和 `<transition>` 一起使用 -->
<transition>
<keep-alive>
<component :is="view"></component>
</keep-alive>
</transition>
被keep-alive包含的组件不会被再次初始化,也就意味着不会重走生命周期函数
被包含在 keep-alive 中的组件,会多出两个生命周期的钩子: activated 与 deactivated:
- activated 当 keep-alive 包含的组件再次渲染的时候触发
- deactivated 当 keep-alive 包含的组件销毁的时候触发
开启keep-alive 生命周期的变化
初次进入时:触发 onMounted
退出后触发 deactivated
再次进入:
只会触发 onActivated
事件挂载的方法等,只执行一次的放在 onMounted中;组件每次进去执行的方法放在 onActivated中
<!-- 引用 -->
<template>
<el-button
v-for="(item, index) in flagArr"
:key="index"
type="primary"
@click="handleCur(item.com)"
>{{ item.name }}</el-button
>
<keep-alive>
<component :is="flagComponent" />
</keep-alive>
</template>
<script>
import { toRefs, reactive, defineAsyncComponent } from "vue";
export default {
components: {
Table: defineAsyncComponent(() => import("@/components/table.vue")),
Form: defineAsyncComponent(() => import("@/components/form.vue")),
},
setup() {
let state = reactive({
flagComponent: "Table",
flagArr: [
{ name: "表格", com: "Table" },
{ name: "表单", com: "Form" },
],
});
let handleCur = (name) => {
state.flagComponent = name;
};
return {
...toRefs(state),
handleCur,
};
},
};
</script>
----------------------------------------------------
<!-- 表单vue -->
<template>
<div>表单</div>
<el-input v-model="fVal" />
</template>
<script>
import { reactive, toRefs,onActivated,onDeactivated,onMounted } from "vue";
export default {
name: "Form",
setup() {
const state = reactive({ fVal: "" });
onMounted(()=>{
console.log("首次进入时触发,onMounted");
})
onActivated(()=>{
console.log("onActivated");
})
onDeactivated(()=>{
console.log("onDeactivated");
})
return {
...toRefs(state),
};
},
};
</script>
<style lang='scss' scoped></style>
----------------------------------------------------------
<!-- 表格vue -->
<template>
<div>表格</div>
<el-input v-model="taVal" />
</template>
<script>
import { reactive, toRefs ,onMounted,onActivated,onDeactivated} from "vue";
export default {
name: "Table",
setup() {
const state = reactive({
taVal: "",
});
onMounted(()=>{
console.log("首次进入时触发,onMounted");
})
onActivated(()=>{
console.log("onActivated");
})
onDeactivated(()=>{
console.log("onDeactivated");
})
return {
...toRefs(state),
};
},
};
</script>
<style lang='scss' scoped></style>
- 属性
keep-alive可以接收3个属性做为参数进行匹配对应的组件进行缓存:
- include包含的组件(可以为字符串,数组,以及正则表达式,只有匹配的组件会被缓存)
- exclude排除的组件(以为字符串,数组,以及正则表达式,任何匹配的组件都不会被缓存)
- max缓存组件的最大值(类型为字符或者数字,可以控制缓存组件的个数)
注:当使用正则表达式或者数组时,一定要使用v-bind
// 只缓存组件name为a或者b的组件
<keep-alive include="a,b">
<component />
</keep-alive>
// 组件name为c的组件不缓存(可以保留它的状态或避免重新渲染)
<keep-alive exclude="c">
<component />
</keep-alive>
// 如果同时使用include,exclude,那么exclude优先于include, 下面的例子只缓存a组件
<keep-alive include="a,b" exclude="b">
<component />
</keep-alive>
// 如果缓存的组件超过了max设定的值5,那么将删除第一个缓存的组件
<keep-alive exclude="c" max="5">
<component />
</keep-alive>
keep-alive 先匹配被包含组件的 name 字段,如果 name 不可用,则匹配当前组件 components 配置中的注册名称。
2.keep-alive 不会在函数式组件中正常工作,因为它们没有缓存实例。
3.当匹配条件同时在 include 与 exclude 存在时,以 exclude 优先级最高(当前vue 2.4.2 version)。比如:包含于排除同时匹配到了组件A,那组件A不会被缓存。
4.包含在 keep-alive 中,但符合 exclude ,不会调用activated和 deactivated。
二十三. transition
transition 标签能够让内部单个组件切换的时候,拥有自定义的动画效果。
- 属性
- name:自定义的名称,用于替换 v-enter-from等状态类的 v,在组件切换时,能给组件在不同的时期添加上对应的类
<transition name='fade'>
//组件
<component />
</transition>
------------------------------------------
/*
enter 对应的是元素插入
leave 对应是元素移除
fade 是transition组件对应的name属性
*/
//开始过度
.fade-enter-from{
background:red;
width:0px;
height:0px;
transform:rotate(360deg)
}
//开始过度了
.fade-enter-active{
transition: all 2.5s linear;
}
//过度完成
.fade-enter-to{
background:yellow;
width:200px;
height:200px;
}
//离开的过度
.fade-leave-from{
width:200px;
height:200px;
transform:rotate(360deg)
}
//离开中过度
.fade-leave-active{
transition: all 1s linear;
}
//离开完成
.fade-leave-to{
width:0px;
height:0px;
}
- 组件的切换动态周期: 周期可以绑定不同的函数
<!-- 生命周期钩子函数 -->
@before-enter="beforeEnter" //对应enter-from
@enter="enter"//对应enter-active
@after-enter="afterEnter"//对应enter-to
@enter-cancelled="enterCancelled"//显示过度打断
@before-leave="beforeLeave"//对应leave-from
@leave="leave"//对应enter-active
@after-leave="afterLeave"//对应leave-to
@leave-cancelled="leaveCancelled"//离开过度打断
// ...
methods: {
// --------
// 进入时
// --------
beforeEnter(el) {
// ...
},
// 当与 CSS 结合使用时
// 回调函数 done 是可选的
enter(el, done) {
// ...
done()
},
afterEnter(el) {
// ...
},
enterCancelled(el) {
// ...
},
// --------
// 离开时
// --------
beforeLeave(el) {
// ...
},
// 当与 CSS 结合使用时
// 回调函数 done 是可选的
leave(el, done) {
// ...
done()
},
afterLeave(el) {
// ...
},
// leaveCancelled 只用于 v-show 中
leaveCancelled(el) {
// ...
}
}
- duration:延迟时间;设置该属性能够让定义的CSS类中的延迟时间失效,改为设定的时间
<transition :duration="1000">...</transition>
//进入(enter)和离开(leave)的持续时间
<transition :duration="{ enter: 500, leave: 800 }">...</transition>
- appear:该属性组件初始化时就执行一次过渡
- mode:模式,能够让组件过渡更加流畅(out-in: 等上一个组件消失再切换到新的组件)
in-out: 新元素先进行进入过渡,完成之后当前元素过渡离开。
out-in: 当前元素先进行离开过渡,完成之后新元素过渡进入。(建议使用)
- css:给css赋值为false,能够让设置的 v-enter等过渡类失效
- enter-active-class,leave-active-class: 能够直接给对应的状态加上指定类名
<transition
leave-active-class="animate__animated animate__bounceInLeft"
enter-active-class="animate__animated animate__bounceInRight"
>
//组件
<component />
</transition>
二十四. 定义全局函数和变量
由于Vue3 没有Prototype 属性 使用 app.config.globalProperties 代替 然后去定义变量和函数
// 之前 (Vue 2.x)
Vue.prototype.$http = () => {}
// 之后 (Vue 3.x)
const app = createApp({})
app.config.globalProperties.$http = () => {}
app.config.globalProperties.aa = "12138"
二十五. getCurrentInstance()
setup的执行时组件对象还没有创建,此时不能使用this来访问组件实例对象,我们可以通过 getCurrentInstance这个函数来返回当前组件的实例对象,也就是当前vue这个实例对象。
请不要把它当作在组合式 API 中获取 this 的替代方案来使用。
getCurrentInstance 只能在 setup 或生命周期钩子中调用。
//main.ts定义变量或者函数
//定义全局函数和变量
app.config.globalProperties.$http = () => 11
app.config.globalProperties.aa = "12138"
------------------------------------------------------------------
//组件使用
//获取全局变量or函数
import {getCurrentInstance } from "vue";
export default {
setup() {
const {appContext} = getCurrentInstance()
console.log(appContext.app.config.globalProperties.aa);
console.log(appContext.app.config.globalProperties.$http());
},
};