「这是我参与2022首次更文挑战的第16天,活动详情查看:2022首次更文挑战」
setup()
setup()接受两个参数:
-
props
-
context:context中包含三个属性
attrs:所有的非prop的attribute;
slots:父组件传递过来的插槽(这个在以渲染函数返回时会有作用);
emit:当我们组件内部需要发出事件时会用到emit(因为我们不能访问this,所以不可以通过 this.$emit发出事件);
setup()的返回值:
setup()可以放回一个对象,该对象中的属性可以在template中使用,相当于options API 中的data属性。
注意:在 setup 中你应该避免使用 this,因为它不会找到组件实例。setup 的调用发生在 data property、computed property 或 methods 被解析之前,所以它们无法在 setup 中被获取。
setup中,如果我们想要访问组件实例,我们需要vue中导出的getCurrentInstance。
import { getCurrentInstance} from "vue";
setup(props,context) {
const instance = getCurrentInstance();
console.log(instance.appContext.config.globalProperties.$name);
}
reactive
我们直接定义的对象不是响应式的,界面中的使用不会随着数据改变而响应式的更新。因此,我们可以使用reactive(),将我们的对象进行包裹,此时我们对象中的值,就是响应式的了。
注意:reative()中只能传入对象或数组,不能传入基本数据类型。
使用:
<template>
<div class="home">
<button @click="fn">点击</button>
<h2>home:{{ info3.age }}</h2>
</div>
</template>
<script>
//导入reactive
import { reactive } from "vue";
export default {
props: ['lz'],
components: {
detail
},
setup(props, context) {
//使用reactive
let info3 = reactive({ age: 10 });
function fn() {
console.log('fn函数被触发');
info3.age = 100;
}
return {
fn,
info3,
}
}
}
</script>
ref
由于reactive中只能传入对象类型,因此我们可以使用ref来传入一个基本数据类型,ref()会给我们返回一个可变的响应式对象。我们传入的值是保存在该对象的value属性中的。
<template>
<div class="home">
<button @click="fn">点击</button>
<h1>{{ age }}</h1>
</div>
</template>
<script>
import { reactive, ref, readonly } from "vue";
import detail from './Detail.vue'
export default {
props: ['lz'],
components: {
detail
},
setup(props, context) {
//定义响应式变量age
let age = ref(25);
function fn() {
console.log('fn函数被触发');
age.value = 19
}
return {
fn,
age
}
}
}
</script>
需要注意的是,我们在template模板中使用,我们不需要[.value],vue会帮助我们自动解包。但是在setup()中,我们还是需要[.value]的,因为不会自动的解包。
模板中的解包是浅层解包,如果ref对象嵌入到对象中了,模板是不会正确解包的。
//这种情况,我们需要写.value
<h1>{{ info.age.value }}</h1>
let age = ref(19);
let info = {
name: 'wang',
age
}
如果将ref对象,放入到reactive()中,作为一个属性。此时ref对象是会自动解包的。无论是在template中,还是setup()函数中。
readonly
readonly可以控制对象只读,readonly会返回原生对象的只读代理。
参数:
- 普通对象
- ref对象
- reactive对象
<template>
<div class="home">
<button @click="fn">点击</button>
<h1>{{ address }}</h1>
<h1>只读:{{ Raddress }}</h1>
</div>
</template>
<script>
import { reactive, ref, readonly } from "vue";
export default {
components: {
},
setup(props, context) {
let address = ref('北京')
let Raddress = readonly(address);
const fn = () => {
console.log('fn被执行');
//address.value='南京'; //可以修改
Raddress.value = '上海'; //不可以修改,Raddress为只读属性
}
return {
fn,
address,
Raddress
}
}
}
</script>
应用:对于向子组件传值,如果我们传递的基本数据类型,当我们在子组件中改变props时,会跑出警告,该值为只读属性。但是如果我们传递的是引用类型的数据,改变其中的某一个属性,vue是检查不到的,因此我们可以使用readonly,对我们的对象进行代理,不允许在子组件中改变props。
如果试图修改,会抛出这样的警告。
toRefs
当我们使用reactive()创建出来响应式对象时,为了使用方便,我们想通过es6中的解构来解构对象,但是这样解构reactive返回的对象时,我们再来修改对象时,无论是修改解构出来的变量还是修改该对象,值都不会改变。因此我们可以使用toRefs来将reactive返回的对象中的属性都转成ref; 那么我们再次进行结构出来的 name 和 age 本身都是 ref的;
这种做法相当于已经在state.name和ref.value之间建立了 链接,任何一个修改都会引起另外一个变化;
示例:
setup(props, context) {
const info = reactive({
name: '张三',
age: 21
});
let { name, age } = toRefs(info);
//修改info.name或者name.value,都是响应式改变的
const fn = () => {
console.log('fn被执行');
// name.value = 'lisi';
info.name = '王五';
console.log(name);
}
return {
fn,
info,
name,
age
}
toRef
如果我们指向转换某一个对象属性,可以使用toRef()。
//参数为:【对象,对象中的属性】
let name = toRef(info, 'name')
shallowRef
创建一个跟踪自身 .value 变化的 ref,但不会使其值也变成响应式的。
const info = shallowRef({
name: '张三',
age: 21,
friends: {
name: 'lisi'
}
});
const fn = () => {
console.log('fn被执行');
//响应式
info.value = {
name: '李四'
};
//不是响应式
info.value.name = '王五'
}
shallowReactive
创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (暴露原始值)。
const info = shallowReactive({
name: '张三',
age: 21,
friends: {
name: 'lisi'
}
});
const fn = () => {
console.log('fn被执行');
//响应式
info.name = '李四'
//不是响应式
info.friends.name = '王五'
}
triggerRef
手动执行与 shallowRef 关联的任何作用 (effect)。
意思是:由于shalowRef只会监听.value是否改变,而不会监听.value中的属性的变化。
例如:
const shallow = shallowRef({
greet: 'Hello, world'
})
//点击事件执行函数
const fn = () => {
console.log('fn被执行');
//修改greet值,界面不会刷新
shallow.value.greet = 'feawfe';
//打印的值会变为 feawfe
console.log(shallow.value.greet);
//手动触发对象内部的变化,但是界面中的值不更新的副作用
triggerRef(shallow);
}
customRef
创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收 track 和 trigger 函数作为参数,并且应该返回一个带有 get 和 set 的对象。
实现简单节流的响应数据:
#userDebounceRef.js文件
import {customRef} from "vue";
export default function (value) {
let timer = null;
return customRef((track, trigger) => {
return {
get() {
track();
return value;
},
set(newValue) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
value = newValue;
trigger();
}, 1000);
},
};
});
}
//组件中使用
const info = DebounceRef('Hello, world');
watchEffect
规则:
- 首先,watchEffect传入的函数会被立即执行一次,并且在执行的过程中会收集依赖;
- 其次,只有收集的依赖发生变化时,watchEffect传入的函数才会再次执行;
//会侦听name和info的变化,name或info变化都会执行该函数
const stop = watchEffect(() => {
console.log('watchEffect' + name.value);
console.log('watchEffect' + info.value);
})
//改变info事件函数
const fn = () => {
console.log('fn被执行');
info.value++;
//info==20时,停止侦听
if (info.value == 20) {
stop();
}
}
//改变name事件函数
const foo = () => {
name.value = 'lisi'
}
watchEffect的返回值是一个函数,执行该函数可以停止侦听。
清除副作用
案例:当数据改变时,我们想用清除之前的请求,可以使用回调函数中的onInvalidate函数,清除之前的请求。
const stop = watchEffect((onInvalidate) => {
console.log('watchEffect' + name.value);
console.log('watchEffect' + info.value);
//清除副作用,当数据改变时,清除之前的请求
onInvalidate(() => {
clearTimeout(timer);
})
//请求数据
let timer = setTimeout(() => {
console.log('请求数据成功');
}, 1000);
})
watchEffect执行时机
默认情况下,副作用函数会在组件更新前执行。但是如果我们想要在组件更新之后执行副作用函数,可以使用flush属性,将其设置为post,默认是 pre
例如:在示例中,
默认情况:
默认情况下,titleRef.value会打印两次,分别为null和
。这是因为setup函数在执行时就会立即执行传入的副作用函数,这个时候DOM并没有挂载,所以打印为null;
而当DOM挂载时,会给titleRef对象赋值新的值,副作用函数会再次执行,打印出来对应的元素;
设为post后:
由于执行setup函数时应该执行一次,但是这次会检测到我们想要它在DOM挂载完之后再执行,因此它推迟了执行,开始渲染DOM。由于渲染DOM时,会给titleRef对象赋值新的值,因此再次触发了副作用函数,由于(Vue 的响应性系统会缓存副作用函数,并异步地刷新它们,这样可以避免同一个“tick” 中多个状态改变导致的不必要的重复调用)vue的特性,第一次的副作用函数别覆盖,因此只会执性一次。
<template>
<div class="home">
<p ref="titleRef">年后</p>
</div>
</template>
<script>
import { reactive, computed, ref, watchEffect } from "vue";
import DebounceRef from '../plugins/userDebounceRef.js'
export default {
setup(props, context) {
//setup函数中使用[ref](相当于this.$refs.titleRef),
//定义ref对象,导出,然后在标签中使用该变量
const titleRef = ref(null);
//
const stop = watchEffect((onInvalidate) => {
console.log(titleRef.value);
}, {
flush: 'post'
})
return {
titleRef,
}
}
}
</script>
watch
可以监听的类型:
- ref对象
- reactive对象
- getter函数(必须引用的是可响应式的对象,如reative和ref)
- 使用数组同时侦听多个数据源
基本使用:
let info = reactive({
name: 'wagn'
})
//监听的属性 回调
watch(info, () => {
console.log('count改变了');
})