官方文档: https://v3.cn.vuejs.org/guide/composition-api-introduction.html
组合式 API (Composition API) 是一系列 API 的集合,使我们可以使用函数而不是声明选项的方式书写 Vue 组件。它是一个概括性的术语,涵盖了以下方面的 API:
ref()
和 reactive()
,使我们可以直接创建响应式状态、计算属性和侦听器。
onMounted()
和 onUnmounted()
,使我们可以在组件各个生命周期阶段添加逻辑。
provide()
和 inject()
,使我们可以在使用响应式 API 时,利用 Vue 的依赖注入系统。
组合式 API 是 Vue 3 及 Vue 2.7 的内置功能。对于更老的 Vue 2 版本,可以使用官方维护的插件 @vue/composition-api
。在 Vue 3 中,组合式 API 基本上都会配合 <script setup>
语法在单文件组件中使用。下面是一个使用组合式 API 的组件示例:
<script setup>
import { ref, onMounted } from 'vue'
// 响应式状态
const count = ref(0)
// 更改状态、触发更新的函数
function increment() {
count.value++
}
// 生命周期钩子
onMounted(() => {
console.log(`计数器初始值为 ${count.value}。`)
})
</script>
<template>
<button @click="increment">点击了:{{ count }} 次</button>
</template>
虽然这套 API 的风格是基于函数的组合,但组合式 API 并不是函数式编程。组合式 API 是以 Vue 中数据可变的、细粒度的响应性系统为基础的,而函数式编程通常强调数据不可变。
如果你对如何通过组合式 API 使用 Vue 感兴趣,可以通过页面左侧边栏上方的开关将 API 偏好切换到组合式 API,然后重新从头阅读指引。
简化的setup函数:
<template>
<h2>{{ username }}</h2>
<button @click="update">update</button>
</template>
<script lang="ts" setup>
import { h } from "vue";
let username = "tom";
let update = () => {
console.log("update2");
};
</script>
<style></style>
返回一个渲染函数,注意会替换页面中的内容,很少使用,慎用。
<template>
<h2>123</h2>
<button>update</button>
</template>
<script lang="ts">
import { h } from "vue";
export default {
setup() {
let username = "tom";
let update = () => {
console.log("update");
};
return () => h("h2", "Hello Setup!");
},
};
</script>
<style></style>
文本与按钮被替换了。
Vue3兼容多数Vue2的内容
<template>
<h2>{{ user }}</h2>
<button @click="change">更新</button>
</template>
<script lang="ts">
import { h } from "vue";
export default {
data() {
return {
user: "tom",
};
},
methods: {
change() {
this.user += "!";
},
},
};
</script>
<style></style>
可以混用,但不建议这么做
<template>
<h2>{{ user }}</h2>
<button @click="change">更新姓名</button>
<hr />
<h2>{{ age }}</h2>
<button @click="update">更新年龄</button>
</template>
<script lang="ts">
import { ref } from "vue";
export default {
data() {
return {
user: "tom",
};
},
methods: {
change() {
this.user += "!";
},
},
setup() {
let age = ref(18);
let update = () => {
age.value++;
};
return { age, update };
},
};
</script>
<style></style>
Vue2.x配置(data、methos、computed...)中可以访问到setup中的属性、方法。
<template>
<h2>{{ user }}</h2>
<button @click="change">methods中的事件,访问setup中的成员</button>
</template>
<script lang="ts">
import { ref } from "vue";
export default {
data() {
return {
user: "tom",
};
},
methods: {
change() {
this.user += this.age;
},
},
setup() {
let age = 18;
return { age };
},
};
</script>
<style></style>
但在setup中不能访问到Vue2.x配置(data、methos、computed...)。
<template>
<h2>{{ user }}</h2>
<button @click="update">setup中的事件,访问data中的数据</button>
</template>
<script lang="ts">
import { ref } from "vue";
export default {
data() {
return {
user: "tom",
};
},
setup() {
function update(this: any) {
this.user += "!!!";
console.log(this.user);
}
return { update };
},
};
</script>
<style></style>
如果有重名, setup优先。
<template>
<h2>{{ user }}</h2>
</template>
<script lang="ts">
import { ref } from "vue";
export default {
data() {
return {
user: "tom",
};
},
setup() {
return { user: "jack" };
},
};
</script>
<style></style>
setup一般不能是一个async函数
<template>
<h2>{{ user }}</h2>
</template>
<script lang="ts">
import { ref } from "vue";
export default {
data() {
return {
user: "tom",
};
},
setup() {
async function f1() {
return { a: 100 };
}
console.log(f1());
return { user: "jack" };
},
};
</script>
<style></style>
const xxx = ref(initValue)
xxx.value
<div>{{xxx}}</div>
Object.defineProperty()
的get
与set
完成的。
reactive
函数。
import { ref, Ref,isRef } from 'vue'
let message: Ref<string | number> = ref("我是message")
let notRef:number = 123
const changeMsg = () => {
message.value = "change msg"
console.log(isRef(message)); //true
console.log(isRef(notRef)); //false
}
下面的示例运行时没有更新姓名
<template>
<h2>姓名:{{ user }}</h2>
<button @click="update">更新</button>
</template>
<script lang="ts">
export default {
setup() {
let user = "tom";
let update = () => {
user += "!";
};
return { user, update };
},
};
</script>
<style></style>
其实user的值是修改了,但是没有响应式的更新视图
原因是此时的user是一个非响应式对象,使用ref可以解决问题
<template>
<h2>姓名:{{ user }}</h2>
<button @click="update">更新</button>
</template>
<script lang="ts">
import { ref } from "vue";
export default {
setup() {
let user = ref("tom");
let update = () => {
user.value += "!";
console.log(user);
};
return { user, update };
},
};
</script>
<style></style>
ref
函数)
const 代理对象= reactive(源对象)
接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)
使用 Object 构造函数上的 defineProperty() 实现。
1、vue2存在的问题
新增属性、删除属性,界面不会更新。
直接通过下标修改数组,界面不会自动更新。
注意:如果要解决上述问题
2、实现响应式的原理
对象类型:通过 Object.defineProperty() 对属性的读取、修改进行拦截(数据劫持)。
数组类型:通过重写更新数组的方法来实现拦截(对数组的变更方法进行了包裹)。
(1)Object.defineProperty() 的基本使用
Object.defineProperty("对象", "属性", {
value: 0, // 属性值
enumerable: true, // 属性是否可被枚举,默认 false
writable: true, // 属性是否可被修改,默认 false
configurable: true, // 属性是否可被删除,默认 false
get() {}, // 获取属性值时调用,此函数需返回属性的属性值
set(value) {}, // 修改属性值时调用,value为修改后的值
})
(2)实现代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script src="./js/vue3.js"></script>
<script>
function updateView(target, key, newValue) {
console.log("视图更新了!", target, key, newValue);
}
function observer(target) {
//如果被观察的对象不是对象类,或为null,则直接返回原对象
if (typeof target !== "object" || target === null) {
return target;
}
//遍历出对象的所有的key
for (let key in target) {
//定义属性,覆盖原有属性
defineReactive(target, key, target[key]);
}
}
//在目标对象上定义属性,目标对象,属性名,值
function defineReactive(target, key, value) {
//如果被定义的值是对象类型则继续观察
if (typeof value === "object") {
observer(value);
} else {
//value在这里起到了闭包保留值的作用
Object.defineProperty(target, key, {
get() {
return value;
},
set(newValue) {
if (newValue !== value) {
updateView(target, key, newValue);
value = newValue;
}
},
});
}
}
let person = {
name: "张果",
address: {
province: "广东",
city: "珠海",
},
};
observer(person);
person.name = "张三";
person.address.city = "深圳";
console.log(person);
</script>
</body>
</html>
(1)、反射 Reflect
Reflect是ES6中新增加的一个对象,并非构造器,该对象中含有多个可完成"元编程(对编程语言进行编程)"功能的静态函数,能方便的对对象进行操作,也可以结合Proxy实现拦截功能
https://www.cnblogs.com/best/p/16291079.html#_lab2_4_0
(2)、代理Proxy
浅层代理:
//定义被代理的对象
var shape = { width: 100 };
//定义代理代理
let proxy = new Proxy(shape, {
get: function (target, key, receiver) {
console.log("get");
//输出被代理的目标对象,属性名称,receiver为getter调用时的this值(当前对象)
console.log(target, key, receiver);
//使用get方法从目标对象中获取值,把取得的值加100
return Reflect.get(target, key, receiver) + 100;
},
set: function (target, key, value, receiver) {
console.log("set");
//输出被代理的目标对象,属性名称,值,receiver为getter调用时的this值(当前对象)
console.log(target, key, value, receiver);
//在目标对象上设置属性值,设置值时将值加100
return Reflect.set(target, key, value + 100, receiver);
},
deleteProperty: function (target, key, receiver) {
console.log("delete");
console.log(target, key);
return Reflect.deleteProperty(target, key, receiver);
},
});
proxy.width = 101;
console.log(proxy.width);
delete proxy.width;
深层代理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
//定义被代理的对象
var shape = { width: 100, location: { x: 100, y: 200 } };
function reactive(target) {
return createReactiveObject(target);
}
function isObject(target) {
return typeof target === "object" && target !== null;
}
function createReactiveObject(target) {
if (!isObject(target)) return target;
return new Proxy(target, {
get: function (target, key, receiver) {
console.log("get");
//输出被代理的目标对象,属性名称,receiver为getter调用时的this值(当前对象)
console.log(target, key, receiver);
//使用get方法从目标对象中获取值
let result = Reflect.get(target, key, receiver);
//递归多层代理,相比于vue2的优势是,vue2默认递归,而vue3中,只要不使用就不会递归。
return isObject(result) ? reactive(result) : result;
},
set: function (target, key, value, receiver) {
console.log("set");
//输出被代理的目标对象,属性名称,值,receiver为getter调用时的this值(当前对象)
console.log(target, key, value, receiver);
//在目标对象上设置属性值
return Reflect.set(target, key, value, receiver);
},
deleteProperty: function (target, key, receiver) {
console.log("delete");
console.log(target, key);
return Reflect.deleteProperty(target, key, receiver);
},
});
}
let proxyShape = reactive(shape);
proxyShape.location.x = 500;
</script>
</body>
</html>
总结:
reactive
转为代理对象。
Object.defineProperty()
的get
与set
来实现响应式(数据劫持)。
.value
,读取数据时模板中直接读取不需要.value
。
.value
。
this.$attrs
。
this.$slots
。
this.$emit
。
<template>
<p>姓氏:<input v-model="user.lastname" /></p>
<p>名字:<input v-model="user.firstname" /></p>
<p>姓名:<input v-model="fullname1" /></p>
<p>姓名:<input v-model="fullname2" /></p>
</template>
<script lang="ts">
import { reactive, ref, computed } from "vue";
export default {
setup() {
let user = reactive({
firstname: "jack",
lastname: "ma",
});
let fullname1 = computed(() => {
return user.firstname + " " + user.lastname;
});
let fullname2 = computed({
get() {
let result = user.firstname;
if (user.lastname) {
result += " " + user.lastname;
}
return result;
},
set(value) {
if (value) {
let users = value.trim().split(" ");
user.firstname = users[0];
user.lastname = users[1] || "";
} else {
user.firstname = "";
user.lastname = "";
}
},
});
return { user, fullname1, fullname2 };
},
};
</script>
<style></style>
运行效果
计算属性的第3种写法(类似方法)
若我们将同样的函数定义为一个方法而不是计算属性,两种方式在结果上确实是完全相同的,然而,不同之处在于计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。
//情况一:监视ref定义的响应式数据 watch(sum,(newValue,oldValue)=>{ console.log('sum变化了',newValue,oldValue) },{immediate:true}) //情况二:监视多个ref定义的响应式数据 watch([sum,msg],(newValue,oldValue)=>{ console.log('sum或msg变化了',newValue,oldValue) }) /* 情况三:监视reactive定义的响应式数据 若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!! 若watch监视的是reactive定义的响应式数据,则强制开启了深度监视 */ watch(person,(newValue,oldValue)=>{ console.log('person变化了',newValue,oldValue) },{immediate:true,deep:false}) //此处的deep配置不再奏效 //情况四:监视reactive定义的响应式数据中的某个属性 watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{immediate:true,deep:true}) //情况五:监视reactive定义的响应式数据中的某些属性 watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{immediate:true,deep:true}) //特殊情况 watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。 watchEffect(()=>{ const x1 = sum.value const x2 = person.age console.log('watchEffect配置的回调执行了') })
const name = toRef(person,'name')
toRefs
与toRef
功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)
reactive
生成的响应式对象转为普通对象。
provide
选项来提供数据,后代组件有一个 inject
选项来开始使用这些数据
reactive
创建的响应式代理
readonly
创建的只读代理
reactive
或者 readonly
方法创建的代理
使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。
我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有