在Vue2中,我们 编写组件的方式是Options API:
对应的属性
中编写对应的功能模块
; p比如data定义数据、methods中定义方法、computed中定义计算属性、watch中监听属性改变
,也包括生命周期钩子
;但是这种代码有一个很大的弊端:
下面我们来看一个非常大的组件,其中的逻辑功能按照颜色进行了划分:
同一个逻辑关注点相关的代码
收集在一起
会更好。Vue Composition API
简称为VCA
。(编写代码)的地方
;setup 函数
;用它来替代之前所编写的大部分其他选项
;methods、computed、watch、data、生命周期
等等;父组件home.vue
<template>
<div>
<home message="hahahaha" id="aaa" class="bbbb"></home>
</div>
</template>
<script>
import Home from './Home.vue';
export default {
components: {
Home
}
}
</script>
子组件Son.vue
<template>
<div>
Home Page
<h2>{{message}}</h2>
<h2>{{title}}</h2>
<h2>当前计数: {{counter}}</h2>
<button @click="increment">+1</button>
</div>
</template>
<script>
export default {
props: {
message: {
type: String,
required: true
}
},
data() {
return {
counter: 100
}
},
/**
* 参数一: props, 父组件传递过来属性
*/
// setup函数有哪些参数?
// setup函数有什么样的返回值
// setup(props, context) {
setup(props, {attrs, slots, emit}) {
console.log(props.message);
console.log(attrs.id, attrs.class);
console.log(slots);
console.log(emit);
return {
title: "Hello Home",
counter: 100
}
},
methods: {
btnClick() {
this.$emit("")
}
}
}
</script>
props
context
setup函数
中想要使用props
,那么不可以通过 this
去获取;props
有直接作为参数传递到setup函数中
,所以我们可以直接通过参数来使用即可
;context
,我们也称之为是一个SetupContext
,它里面包含 三个属性
:
attrs
:所有的非prop的attribute;slots
:父组件传递过来的插槽(这个在以渲染函数返回时会有作用);emit
:当我们组件内部需要发出事件时会用到emit(因为我们不能访问this,所以不可以通过 this.$emit发出事件);this并没有指向当前组件实例
;data、computed、methods
等都没有被解析;无法在setup中获取this
;那么这是什么原因呢?为什么就可以变成响应式的呢?
使用reactive函数处理我们的数据之后
,数据再次被使用
时就会进行依赖收集
;数据发生改变
时,所有收集到的依赖
都是进行对应的响应式
操作(比如更新界面);data选项
,也是在内部交给了reactive函数
将其编程响应式对象的;<template>
<div>
Home Page
<h2>{{message}}</h2>
<h2>当前计数: {{state.counter}}</h2>
<button @click="increment">+1</button>
</div>
</template>
<script>
import { reactive } from 'vue';
export default {
props: {
message: {
type: String,
required: true
}
},
setup() {
const state = reactive({
counter: 100
})
// 局部函数
const increment = () => {
state.counter++;
console.log(state.counter);
}
return {
state,
increment
}
}
}
</script>
<template>
<div>
Home Page
<h2>{{message}}</h2>
<!-- 当我们在template模板中使用ref对象, 它会自动进行解包 -->
<h2>当前计数: {{counter}}</h2>
<button @click="increment">+1</button>
<show-message :message="counter"></show-message>
</div>
</template>
<script>
import { ref } from 'vue';
export default {
props: {
message: {
type: String,
required: true
}
},
setup() {
// counter编程一个ref的可响应式的引用
// counter = 100;
let counter = ref(100);
// 局部函数
const increment = () => {
counter.value++;
console.log(counter.value);
}
return {
counter,
increment
}
}
}
</script>
这个时候Vue3给我们提供了另外一个API:ref API
这里有两个注意事项:
<template>
<div>
Home Page
<h2>{{message}}</h2>
<!-- 当我们在template模板中使用ref对象, 它会自动进行解包 -->
<h2>当前计数: {{counter}}</h2>
<!-- ref的解包只能是一个浅层解包(info是一个普通的JavaScript对象) -->
<h2>当前计数: {{info.counter.value}}</h2>
<!-- 当如果最外层包裹的是一个reactive可响应式对象, 那么内容的ref可以解包 -->
<h2>当前计数: {{reactiveInfo.counter}}</h2>
<button @click="increment">+1</button>
</div>
</template>
<script>
import { ref, reactive } from 'vue';
export default {
props: {
message: {
type: String,
required: true
}
},
setup() {
let counter = ref(100);
const info = {
counter
}
const reactiveInfo = reactive({
counter
})
// 局部函数
const increment = () => {
counter.value++;
console.log(counter.value);
}
return {
counter,
info,
reactiveInfo,
increment
}
}
}
</script>
在readonly的使用过程中,有如下规则:
preadonly返回的对象都是不允许修改的;
但是经过readonly处理的原来的对象是允许被修改的;
p其实本质上就是readonly返回的对象的setter方法被劫持了而已;
import { reactive, toRefs, toRef } from 'vue';
如果我们使用ES6的解构语法,对reactive返回的对象进行解构获取值,那么之后无论是修改结构后的变量,还是修改reactive返回的state对象,数据都不再是响应式的:
那么有没有办法让我们解构出来的属性是响应式的呢?
setup() {
const info = reactive({name: "why", age: 18});
// 1.toRefs: 将reactive对象中的所有属性都转成ref, 建立链接
let { name, age } = toRefs(info);
return {
name,
age
}
}
这种做法相当于已经在state.name和ref.value之间建立了 链接,任何一个修改都会引起另外一个变化;
如果我们只希望转换一个reactive对象中的属性为ref, 那么可以使用toRef的方法:
setup() {
const info = reactive({name: "why", age: 18});
let { name } = info;
let age = toRef(info, "age");
const changeAge = () => {
age.value++;
}
return {
name,
age,
changeAge
}
}
在前面我们讲解过计算属性computed:当我们的某些属性是依赖其他状态时,我们可以使用计算属性来处理
如何使用computed呢?
方式一:接收一个getter函数,并为 getter 函数返回的值,返回一个不变的 ref 对象;
方式二:接收一个具有 get 和 set 的对象,返回一个可变的(可读写)ref 对象;
<template>
<div>
<h2>{{fullName}}h2>
<button @click="changeName">修改firstNamebutton>
div>
template>
<script>
import { ref, computed } from 'vue';
export default {
setup() {
const firstName = ref("Kobe");
const lastName = ref("Bryant");
// 1.用法一: 传入一个getter函数
// computed的返回值是一个ref对象
const fullName = computed(() => firstName.value + " " + lastName.value);
// 2.用法二: 传入一个对象, 对象包含getter/setter
const fullName = computed({
get: () => firstName.value + " " + lastName.value,
set(newValue) {
const names = newValue.split(" ");
firstName.value = names[0];
lastName.value = names[1];
}
});
const changeName = () => {
// firstName.value = "James"
fullName.value = "coder";
}
return {
fullName,
changeName
}
}
}
</script>
在前面的Options API中,我们可以通过watch选项来侦听data或者props的数据变化,当数据变化时执行某一些操作。
在Composition API中,我们可以使用watchEffect和watch来完成响应式数据的侦听;
自动收集响应式数据的依赖
;手动指定侦听的数据源
;**当侦听到某些响应式数据变化时,我们希望执行某些操作,这个时候可以使用 watchEffect。 **
我们来看一个案例:
首先,watchEffect传入的函数会被立即执行一次,并且在执行的过程中会收集依赖;
其次,只有收集的依赖发生变化时,watchEffect传入的函数才会再次执行;
<template>
<div>
<h2>{{name}}-{{age}}</h2>
<button @click="changeName">修改name</button>
<button @click="changeAge">修改age</button>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
setup() {
// watchEffect: 自动收集响应式的依赖
const name = ref("why");
const age = ref(18);
const changeName = () => name.value = "kobe"
const changeAge = () => age.value++
watchEffect(() => {
console.log("name:", name.value, "age:", age.value);
});
return {
name,
age,
changeName,
changeAge
}
}
}
</script>
如果在发生某些情况下,我们希望停止侦听,这个时候我们可以获取watchEffect的返回值函数,调用该函数即可。
<template>
<div>
<h2>{{name}}-{{age}}</h2>
<button @click="changeName">修改name</button>
<button @click="changeAge">修改age</button>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
setup() {
// watchEffect: 自动收集响应式的依赖
const name = ref("tom");
const age = ref(18);
const stop = watchEffect(() => {
console.log("name:", name.value, "age:", age.value);
});
const changeName = () => name.value = "kobe"
const changeAge = () => {
age.value++;
if (age.value > 25) {
stop();
}
}
return {
name,
age,
changeName,
changeAge
}
}
}
</script>
什么是清除副作用呢?
比如在开发中我们需要在侦听函数中执行网络请求,但是在网络请求还没有达到的时候,我们停止了侦听器,
或者侦听器侦听函数被再次执行了。
那么上一次的网络请求应该被取消掉,这个时候我们就可以清除上一次的副作用;
在我们给watchEffect传入的函数被回调时,其实可以获取到一个参数:onInvalidate
<template>
<div>
<h2>{{name}}-{{age}}</h2>
<button @click="changeName">修改name</button>
<button @click="changeAge">修改age</button>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
setup() {
// watchEffect: 自动收集响应式的依赖
const name = ref("tom");
const age = ref(18);
const stop = watchEffect((onInvalidate) => {
const timer = setTimeout(() => {
console.log("网络请求成功~");
}, 2000)
// 根据name和age两个变量发送网络请求
onInvalidate(() => {
// 在这个函数中清除额外的副作用
// request.cancel()
clearTimeout(timer);
console.log("onInvalidate");
})
console.log("name:", name.value, "age:", age.value);
});
const changeName = () => name.value = "kobe"
const changeAge = () => {
age.value++;
if (age.value > 25) {
stop();
}
}
return {
name,
age,
changeName,
changeAge
}
}
}
</script>
在讲解 watchEffect执行时机之前,我们先补充一个知识:在setup中如何使用ref或者元素或者组件?
其实非常简单,我们只需要定义一个ref对象,绑定到元素或者组件的ref属性上即可;
<template>
<div>
<h2 ref="title">哈哈哈</h2>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
setup() {
const title = ref(null);
watchEffect(() => {
console.log(title.value);
}, {
flush: "post"
})
return {
title
}
}
}
</script>
默认情况下,组件的更新会在副作用函数执行之前:
如果我们希望在副作用函数中获取到元素,是否可行呢?
<template>
<div>
<h2 ref="title">哈哈哈</h2>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
setup() {
const title = ref(null);
watchEffect(() => {
console.log(title.value);
}, {
flush: "post"
})
return {
title
}
}
}
</script>
我们会发现打印结果打印了两次:
如果我们希望在第一次的时候就打印出来对应的元素呢?
与watchEffect的比较,watch允许我们:
watch侦听函数的数据源有两种类型:
<template>
<div>
<h2 ref="title">{{info.name}}</h2>
<button @click="changeData">修改数据</button>
</div>
</template>
<script>
import { ref, reactive, watch } from 'vue';
export default {
setup() {
const info = reactive({name: "why", age: 18});
// 1.侦听watch时,传入一个getter函数
watch(() => info.name, (newValue, oldValue) => {
console.log("newValue:", newValue, "oldValue:", oldValue);
})
// 2.传入一个可响应式对象: reactive对象/ref对象
// 情况一: reactive对象获取到的newValue和oldValue本身都是reactive对象
// watch(info, (newValue, oldValue) => {
// console.log("newValue:", newValue, "oldValue:", oldValue);
// })
// 如果希望newValue和oldValue是一个普通的对象
watch(() => {
return {...info}
}, (newValue, oldValue) => {
console.log("newValue:", newValue, "oldValue:", oldValue);
})
// 情况二: ref对象获取newValue和oldValue是value值的本身
// const name = ref("why");
// watch(name, (newValue, oldValue) => {
// console.log("newValue:", newValue, "oldValue:", oldValue);
// })
const changeData = () => {
info.name = "kobe";
}
return {
changeData,
info
}
}
}
</script>
如果我们希望侦听一个数组或者对象,那么可以使用一个getter函数,并且对可响应对象进行解构:
<template>
<div>
<h2 ref="title">{{info.name}}</h2>
<button @click="changeData">修改数据</button>
</div>
</template>
<script>
import { ref, reactive, watch } from 'vue';
export default {
setup() {
// 1.定义可响应式的对象
const info = reactive({name: "why", age: 18});
const name = ref("why");
// 2.侦听器watch
watch([() => ({...info}), name], ([newInfo, newName], [oldInfo, oldName]) => {
console.log(newInfo, newName, oldInfo, oldName);
})
const changeData = () => {
info.name = "kobe";
}
return {
changeData,
info
}
}
}
</script>