setup
一、概念
setup是所有Composition API(组合api)“表演的舞台”,组件中所用的到数据、方法等等,均要配置在setup中,但在setup函数中定义的变量和方法最后都需要return出去,不然无法在模版中使用
二、注意点
1、由于在执行 setup函数的时候,还没有执行 Created 生命周期方法,所以在 setup 函数中,无法使用 data 和 methods 的变量和方法
2、由于我们不能在 setup函数中使用 data 和 methods,所以 Vue 为了避免我们错误的使用,直接将 setup函数中的this修改成了 undefined
3、setup函数只能是同步的不能是异步的
三、参数
1、props
值为对象,包含组件外部传递过来且组件内部声明接收了的属性
setup 函数中的 props 是响应式的,当传入新的 prop 时,它将被更新
export default {
props: {
title: String
},
setup(props) {
console.log(props.title)
}
}
不能用es6的方法对props直接进行解构,会消除props的响应性
如果需要对props进行解构,可在setup函数中通过toRefs函数来完成此操作
import { toRefs } from 'vue'
setup(props) {
const { title } = toRefs(props)
console.log(title.value)
}
如果title是可选的prop,及传入的参数可能没有title,toRef函数不会为title创建一个热敷,此时可以通过toRef来实现
import { toRef } from 'vue'
setup(props) {
const title = toRef(props, 'title')
console.log(title.value)
}
2、context
上下文对象
export default {
setup(props, context) {
console.log(context.attrs)
console.log(context.slots)
console.log(context.emit)
console.log(context.expose)
}
}
1)attrs 非响应式对象
值为对象,包含组件外补传递过来,但没有在peops配置中声明的属性,相当于this.$attrs
2)slots 非响应式对象
收到的插槽内容,相当于this.$slots
3)emit 触发事件
分发自定义事件的函数,相当于this.$emits
4)expose
暴露公共函数
context是一个普通的javascripts对象,因此不具备响应式,可以直接进行解构
export default {
setup(props, { attrs, slots, emit, expose }) {
...
}
}
四、返回参数
setup函数的两种返回值:
(1)若返回一个对象,则对象中的属性、方法,在模板中均可以直接使用。
(2)若返回一个渲染函数,则可以自定义渲染内容
import {h} from 'vue'
setup() {
// 数据
let name = 'xxx'
let age = 18
// 方法
function sayHello() {
alert(`${name}${age}岁`)
}
// 返回一个对象(常用)
return {
name,
age,
sayHello
}
// 返回一个渲染函数
return () => {h('h1', 'xxxxxx')}
}
五、语法糖
1、使用方法
setup的语法糖即在script1中直接写setup
使用setup语法糖后,不用写setup函数;组件只需要引入不需要注册;属性和方法也不需要再返回,可以直接在template模板中使用
<script lang="ts" setup>
import {ref} from 'vue';
//此时注册的变量或方法可以直接在template中使用而不需要导出
const numb = ref(0);
let func = ()=>{
numb.value++;
}
</script>
2、setup语法糖中新增的api
1)defineProps
子组件接收父组件中传来的props
父组件代码
<template>
<my-component @click="func" :num="num"></my-component>
</template>
<script lang="ts" setup>
import {ref} from 'vue';
import myComponent from '@/components/myComponent.vue';
const num = ref(0);
let func = ()=>{
num.value++;
}
</script>
子组件代码
<template>
<div>{{num}}</div>
</template>
<script lang="ts" setup>
import {defineProps} from 'vue';
defineProps({
num:{
type:Number,
default:NaN
}
})
</script>
2)defineEmits
子组件调用父组件中的方法
父组件代码
<template>
<my-component @addNumb="func" :numb="numb"></my-component>
</template>
<script lang="ts" setup>
import {ref} from 'vue';
import myComponent from '@/components/myComponent.vue';
const numb = ref(0);
let func = ()=>{
numb.value++;
}
</script>
子组件代码
<template>
<div>{{numb}}</div>
<button @click="onClickButton">数值加1</button>
</template>
<script lang="ts" setup>
import {defineProps,defineEmits} from 'vue';
defineProps({
numb:{
type:Number,
default:NaN
}
})
const emit = defineEmits(['addNumb']);
const onClickButton = ()=>{
//emit(父组件中的自定义方法,参数一,参数二,...)
emit("addNumb");
}
</script>
3)defineExpose
子组件暴露属性,可以在父组件中拿到
子组件代码
<template>
<div>子组件中的值{{numb}}</div>
<button @click="onClickButton">数值加1</button>
</template>
<script lang="ts" setup>
import {ref,defineExpose} from 'vue';
let numb = ref(0);
function onClickButton(){
numb.value++;
}
//暴露出子组件中的属性
defineExpose({
numb
})
</script>
父组件代码
<template>
<my-comp ref="myComponent"></my-comp>
<button @click="onClickButton">获取子组件中暴露的值</button>
</template>
<script lang="ts" setup>
import {ref} from 'vue';
import myComp from '@/components/myComponent.vue';
//注册ref,获取组件
const myComponent = ref();
function onClickButton(){
//在组件的value属性中获取暴露的值
console.log(myComponent.value.numb) //0
}
//注意:在生命周期中使用或事件中使用都可以获取到值,
//但在setup中立即使用为undefined
console.log(myComponent.value.numb) //undefined
const init = ()=>{
console.log(myComponent.value.numb) //undefined
}
init()
onMounted(()=>{
console.log(myComponent.value.numb) //0
})
</script>
ref和reactive
一、ref函数
作用
定义一个响应式的数据,在数据改变的同时能够引发图层的变化
语法
const xxx = ref(initValue)
无初始值设置则为null
(1)创建一个包含响应式数据的引用对象(reference对象)
(2)在js中操作数据:xxx.value
(3)在模板template中读取数据:不需要.value,直接
注意:ref既可以接受基本类型也可以接受对象类型,对于基本类型的数据,响应式依然是 依靠object.defineProperty()的get和set完成的,即通过给value属性设置setter和getter实 现数据劫持,创造一种任意值的引用并能够不丢失响应性地随意传递
当数据类型为对象时,更推荐使用reactive来完成
二、reactive函数
作用
定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数,否则不具备响应式)
语法
const 代理对象 = reactive(源对象)
接收一个对象(或数组),返回一个代理对象(proxy的实例对象)
reactive定义的响应式把数据是深层次的
内部基于ES6的Proxy实现,通过代理对象操作元对象内部数据进行操作,本质是将每一层数据解析成proxy对象,reactive响应式默认都是递归的,改变某一层的值都会递归的调用一遍,重新渲染dom
三、ref和reactive的区别
从定义数据角度对比:
ref用来定义:基本类型数据
reactive用来定义:对象(或数组)类型数据
备注:ref也可以用来定义对象(或数组)类型数据,它内部会自动通过reactive转为代理对象
从原理角度对比:
ref通过Object.defineProperty()的get和set来实现响应式(数据劫持)
reactive通过使用Proxy来实现响应式(数据劫持),并通过Reflect操作源对象内部的数据
从使用角度对比:
ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value
reactive定义的数据:操作数据与读取数据均不需要.value
从响应式对比:
使用ref创建的响应式值,本身就是响应式的,并不依赖于其他对象
而用reactive创建的响应式对象,整个对象是响应式的,但对象里的每一项都是普通的值,当你把它用展开运算符展开后,整个对象的普通值都不是响应式的
如果需要展开reactive创建的响应式对象又不想他们失去响应式的时候,可以利用toRefs来进行转换
注意,ref本质也是reactive,ref(obj)等价于reactive({value:obj})
生命周期
vue2.0->vue3.0
// 在beforeCreate执行前执行,组件实例创建前,执行
beforeCreate -> 请使用 setup()
created -> 请使用 setup()
// 挂载DOM前执行
beforeMount -> onBeforeMount
// 挂载DOM后执行
mounted -> onMounted
// 更新组件前执行
beforeUpdate -> onBeforeUpdate
// 更新组件后执行
updated -> onUpdated
// 卸载销毁前 执行
beforeDestroy -> onBeforeUnmount
// 卸载销毁后执行
destroyed -> onUnmounted
errorCaptured -> onErrorCaptured
计算属性
computed又被称作计算属性,用于动态的根据某个值或某些值的变化,来产生对应的变化,computed具有缓存性,当依赖的值无变化时,不会引起computed声明值的变化
一、computed创建只读计算属性
给 computed 传入一个函数,可以得到一个只读的计算属性:
const count = ref(1)
// 创建一个计算属性,使其值比 count 大 1
const bigCount = computed(() => count.value + 1)
console.log(bigCount.value) // 输出 2
bigCount.value++ // error 不可写
二、computed创建可读可写的计算属性
const count = ref(1)
// 创建一个 computed 计算属性,传入一个对象
const bigCount = computed({
// 取值函数
get: () => (count.value + 1),
// 赋值函数
set: val => {
count.value = val - 1
}
})
// 给计算属性赋值的操作,会触发 set 函数
bigCount.value = 9
// 触发 set 函数后,count 的值会被更新
console.log(count.value) // 8
监听属性
对指定声明的属性实行监听
一、监听ref创建的基本数据类型
1、监听一个数据
<script>
import { watch, ref} from 'vue';
export default {
name:"",
setup(){
let url=ref("www.webtools.wang");
watch(url,(newvalue,oldvalue)=>{
console.log(newvalue,oldvalue)
},{immediate:false})
return {
url
}
}
}
</script>
2、监听多个数据
<script>
import { watch, ref} from 'vue';
export default {
name:"",
setup(){
let url=ref("www.webtools.wang");
let sum=ref(1);
watch([sum,url],(newvalue,oldvalue)=>{
console.log(newvalue,oldvalue)
},{immediate:false})
return {
sum,
url
}
}
}
</script>
二、监听ref创建的对象
需要开启深度监听
<script>
import {ref,watch} from 'vue'
export default {
name:"",
setup(){
let person=ref({
name:"chenxuan",
age:18
})
// 此处监听不到
// watch(person,(newvalue,oldvalue)=>{
// console.log("监听到了变化!")
// })
// 解决方法1
// 使用ref创建引用类型的响应数据,在vue内部最终还是通过reactive实现的
// person.value 相当于在监听reactive创建的响应数据
// watch(person.value,(newvalue,oldvalue)=>{
// console.log("监听到了age变化!")
// })
// 解决方法2,开启深度监听
watch(person.value,(newvalue,oldvalue)=>{
console.log("监听到了age变化!")
},{deep:true})
return{
person
}
}
}
</script>
三、监听reactive创建的对象
watch(person,(newvalue,oldvalue)=>{
console.log("监听到了")
})
四、监听reactive定义的数据的某一个属性
如果监听对象中的某个属性,需要使用箭头函数的方式
监听更少的数据有利于提高性能
watch(()=>person.name,(newvalue,oldvalue)=>{
console.log('监听到了')
})
五、关于配置选项
const obj = reactive({
msg: {
info: 'ooo'
}
})
watch(() => obj.msg,(v1, v2) => {
console.log(v1, v2)
},
{
// 首次渲染组件就触发一次
immediate: true,
// 开启深度监听,对象里面的数据如果发生变化也会被侦听到
// 如果监听的数据是一个比较长的表达式,那么需要用一个函数的方式
// 但是写成函数形式之后,里层的数据变化不到,所以需要添加deep选项
deep: true
}
)
keep-alive
概念
keep-alive是vue中的一个内置组件,通常用它来包裹一个动态组件,keep-alive 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
作用是将组件缓存在内存当中,防止重复渲染DOM,属于消耗内存获取速度。
它有两个特殊的生命周期钩子activated 和 deactivated,在vue3.0里面生命周期函数前面都要加上on,onActivated,onDeactivated。当组件在使用了keep-alive包裹时,正常的生命周期函数mounted和unmounted将不会执行,取而代之的是为它新增的这个两个特殊钩子函数。
属性
include:只有名称匹配的组件会被缓存;类型可以是数组、字符串或者正则。
exclude:名称匹配的组件不会被缓存;类型可以是数组、字符串或者正则。
max:最多可以缓存多少组件实例。
在vue3.0中使用keep-alive
<router-view v-slot="{ Component, route }" id="pageWrapper">
<transition :name="transitionName" mode="out-in">
<keep-alive :include="cacheRouter">
<component :is="Component" />
</keep-alive>
</transition>
</router-view>
设置被缓存的路由
1、直接写死
把需要缓存的路由name写到一个数组中,但这种方法不够灵活
cacheRouter: ['home', 'order'];
2、通过设置路由原信息
在需要被缓存的组件的meta里面添加keepAlive标记
export const routes: RouteRecordRaw[] = [
{
path: '/myLogin',
name: 'myLogin',
component: () => import('@/views/login/login.vue'),
meta: {
title: '登录',
index: 1,
keepAlive: true,
},
},
];
let cacheList: any[] = [];
const keepAliveView = (_route: RouteRecordRaw[], _cache: RouteRecordName[]): void => {
_route.forEach((item) => {
item.meta?.keepAlive && item.name && _cache.push(item.name);
});
};
//routes 路由配置数组
keepAliveView(routes, cacheList);
export default cacheList;