Composition API
setup
- 新的option, 所有的组合API函数都在此使用, 只在初始化时执行一次
- 函数如果返回对象, 对象中的属性或方法, 模板中可以直接使用
- setup是组合API入口
<template>
<div>{
{
str}}</div>
</template>
<script lang="ts">
export default {
name: 'App',
setup() {
const str= 'hello Vue3'
return {
str
}
}
}
</script>
// 输出 hello Vue3
setup细节
setup执行的时机
- 在beforeCreate之前执行(一次), 此时组件对象还没有创建
- this是undefined, 不能通过this来访问data/computed/methods / props
- 其实所有的composition API相关回调函数中也都不可以
setup的返回值
- 一般都返回一个对象: 为模板提供数据, 也就是模板中可以直接使用此对象中的所有属性/方法
- 返回对象中的属性会与data函数返回对象的属性合并成为组件对象的属性
- 返回对象中的方法会与methods中的方法合并成为组件对象的方法
- 如果有重名, setup优先
注意:
- 一般不要混合使用: methods中可以访问setup提供的属性和方法, 但在setup方法中不能访问data和methods,因为this还没创建出来
- setup不能是一个async函数: 因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性数据
- 父组件
<template>
<div>测试文件</div>
<div>{
{msg}}</div>
<div><button @click="msg += '+'">改变</button></div>
<hr>
<Child :msg="msg"></Child>
<!--:msg="msg" 向子组件传值-->
</template>
<script lang="ts">
import {
ref} from 'vue'
import Child from "./components/Child.vue";
export default {
name: 'App',
components: {
Child},
setup(){
const msg=ref('hello vue3')
return {
msg
}
},
}
</script>
- 子组件
<template>
<div>{
{msg}}</div>
<div>子组件{
{showMsg2}}</div>
</template>
<script>
export default {
name: "Child",
props: ['msg'], // 接受父组件信息
/*
* setup只执行一次,在 beforeCreate之前就执行了
* setup执行时,当前组件还创建出来,也就是组件实例对象this不能用
* */
// 初始化开始,执行前创建el
beforeCreate() {
console.log('beforeCreate执行了')
},
// 界面渲染完成
mounted() {
console.log('mounted执行了',this)
},
setup(){
console.log('setup执行了',this)
const showMsg1 = () => {
console.log('setup中的showMsg1')
}
return {
showMsg1
}
},
// 数据
data(){
return {
num1: 10
}
},
// 方法
methods: {
showMsg2(){
console.log('methods中的showmsg2')
}
}
}
</script>
<style scoped>
</style>
/*
* setup执行了 undefined // 先执行setup 因为还未初始化完,所以this未定义
* beforeCreate执行了 // 创建el完成后执行回调beforeCreate
* Proxy {showMsg2: ƒ, …} // 父子组件渲染完成后执行回调
*/
- setup的参数
setup(props, context) / setup(props, {attrs, slots, emit})
- props: 包含props配置声明且传入了的所有属性的对象
- attrs: 包含没有在props配置中声明的属性的对象, 相当于 this.$attrs
- slots: 包含所有传入的插槽内容的对象, 相当于 this.$slots
- emit: 用来分发自定义事件的函数, 相当于 this.$emit
- App.vue
<template>
<div>测试文件</div>
<div>{
{msg}}</div>
<hr>
<Child :msg="msg" msg2="context.attrs中测试文字" @X="X"></Child>
</template>
<script lang="ts">
import Child from './components/Child.vue' // 引入子组件
import {
ref} from "vue"; // 引入双向绑定模板
export default {
name: 'App',
components: {
Child},
setup(props,context){
const msg = ref('hello vue3');
function X (txt: string) {
msg.value += txt
}
return {
X,
msg
}
},
}
</script>
- Child.vue
<template>
<div>{
{msg}}</div>
<div><button @click="emitX">分发事件</button></div>
</template>
<script>
export default {
name: "Child",
props: ['msg'],
setup(props,context){
console.log(props.msg) // hello vue3
console.log(context.attrs.msg2) // context.attrs中测试文字(属性中的值)
const showMsg=() => {
console.log("showMsg方法")
}
function emitX () {
// 回调函数
context.emit('X','++') // 每次hello vue3后面’++‘
}
return {
emitX,
showMsg
}
},
methods: {
showMsg2(){
console.log('methods中的showmsg2')
}
}
}
</script>
<style scoped>
</style>
ref
- 作用: 定义一个响应式的数据
- 语法: const xxx = ref(initValue)
- 返回的是一个ref对象,对象中value属性,如果需要对数据操作,通过ref对象调用value属性方式进行操作
- 操作数据: xxx.value
- HTML模板中操作数据: 不需要.value
- 一般用来定义一个基本类型的响应式数据
<template>
<div>{
{
str}}</div>
<div>{
{
count}}</div>
<div><button @click="updateCount">改变</button></div>
</template>
<script lang="ts">
import {
ref} from 'vue'
export default {
name: 'App',
setup() {
const str= 'hello Vue3'
let count= ref(0)
function updateCount(){
count.value ++
}
return {
str,
count,
updateCount
}
}
}
</script>
reactive
- 作用: 定义多个数据的响应式
- const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象
- 响应式转换是“深层的”:会影响对象内部所有嵌套的属性
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
<template>
<div>{
{
user}}</div>
<div><button @click="updateCount">改变</button></div>
</template>
<script lang="ts">
import {
reactive, ref} from 'vue'
export default {
name: 'App',
setup() {
/*
* 把数据变成响应式的
* 返回的是一个Proxy对象,被代理的对象是小括号中的对象
* 注意不能直接用方法更新目标对象的值target.属性方式
* 只能代理对象user来更改
* */
const user = reactive({
name: "jack",
age: 20,
sex: '男'
})
function updateCount(){
user.name= 'rose',
user.age=20,
user.sex='女'
}
return{
user,
updateCount
}
}
}
</script>
vue2与vue3响应式区别
vue2的响应式
- 对象: 通过defineProperty对对象的已有属性值的读取和修改进行劫持(监视/拦截)
- 问题:对象直接新添加的属性或删除已有属性, 界面不会自动更新,比如表单都是通过v-model双向绑定data里面的数据然后修改数据的
- 数组: 通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持
- 问题: 直接通过下标替换元素或更新length, 界面不会自动更新 arr[1] = {}
Object.defineProperty(data, 'count', {
get () {
},
set () {
}
})
Vue3的响应式
- 通过Proxy(代理): 拦截对data任意属性的任意(13种)操作, 包括属性值的读写, 属性的添加, 属性的删除等
- handler: 包含捕捉器(trap)的占位符对象,可译为处理器对象,也就是用来监视数据变化
- traps提供属性访问的方法。这类似于操作系统中捕获器的概念
- target被 Proxy 代理虚拟化的对象。它常被作为代理的存储后端,也就是目标对象
- 通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作
- Vue3的响应是深度式的响应
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Proxy 与 Reflect</title>
</head>
<body>
<script>
// 目标对象
const user = {
name: "John",
age: 12
};
/*
* proxyUser是代理对象, user是被代理对象
* 后面所有的操作都是通过代理对象来操作被代理对象内部属性
* Reflect,反射用来处理目标对象
*/
const proxyUser = new Proxy(user, {
get(target, prop) {
console.log('劫持get()', prop)
return Reflect.get(target, prop)
},
set(target, prop, val) {
console.log('劫持set()', prop, val)
return Reflect.set(target, prop, val); // (2)
},
deleteProperty (target, prop) {
console.log('劫持delete属性', prop)
return Reflect.deleteProperty(target, prop)
}
});
// 读取属性值
console.log(proxyUser===user)
//false
console.log(proxyUser.name, proxyUser.age)
/* 劫持get() name
* 劫持get() age
* John 12
*/
// 设置属性值
proxyUser.name = 'bob'
// 劫持get() name bob
proxyUser.age = 13
// 劫持set() age 13
console.log(user)
//{name: 'bob', age: 13}
// 添加属性
proxyUser.sex = '男'
// 劫持set() sex 男
console.log(user)
// {name: 'bob', age: 13, sex: '男'}
// 删除属性
delete proxyUser.sex
// 劫持delete属性 sex
console.log(user)
// {name: 'bob', age: 13}
</script>
</body>
</html>