执行时机: setup 代替了之前vue2的 beforeCreate 和 created 钩子,最早执行的钩子(比beforeCreate 还早)
特点: 在setup当中获取不到this
说明: [以后]所有页面中用到的东西都写在setup这个钩子中return出去
新的option, 所有的组合API函数都在此使用, 只在初始化时执行一次
函数如果返回对象, 对象中的属性或方法, 模板中可以直接使用
- 页面中使用的数据需要用ref包裹一下,变成一个响应式对象,然后return出去
- 在页面中使用这个数据的时候直接 在插值语法中写这个数据即可,页面中自动会展示这个对象的.value属性
- 在js中修改ref包裹的数据的时候,需要.value取修改
obj是元对象、reactive代理、obj1是代理对象
const obj = {
name: '张三'
}
const obj1 = reactive(obj);
元对象经过 代理 生成了一个代理对象
代理对象是有响应式的,元对象没有
元对象修改的时候拦截不到设置值和获取值,代理对象在设置值和获取值的时候可以拦截到更新页面
修改代理对象本质上还是在修改元对象
问题: ref和reactive都是把数据变成响应式,有什么差别?(基本数据类型、引用数据类型)
不同点:
ref看着比reactive牛批
在js中书写的时候,修改对象的属性:
总结: 在js中 ref需要.value修改,reactive生成的proxy对象直接.属性修改即可
相同点:
注意理解:当ref包裹一个对象的时候
正儿八经不会掺和着一块使用 组合式API 和 选项式API,以后全部写在
setup(组合式API) 和 methods(选项式API) 配合使用
setup() {
let message = ref('我爱你')
return {
message
}
}
methods: {
changeMessage () {
console.log(this.message)
this.message = "我爱你,高圆圆"
},
}
setup(组合式API) 和 data(选项式API) 配合使用
data(){
return {
text:'咱俩'
}
}
setup() {
const ins = getCurrentInstance(); // 获取到当前实例,getCurrentInstance从vue中引用
console.log(ins);
const changeText = () => {
ins.data.text = "但是我们不合适"
}
}
结论:在data和setup中如果有相同的数据,谁配置在后面展示的就是谁
与computed配置功能一致
只有getter
有getter和setter
vue2写法
computed: {
fullName() {
return firstName.value + '-' + lastName.value;
}
}
vue3写法
const fullName = computed(() => {
return firstName.value + '-' + lastName.value;
})
vue2写法
computed: {
fullName: {
get() {},
set() {}
}
}
vue3写法
const fullName = computed({
get() {
return firstName.value + '-' + lastName.value
},
set(val) {
firstName.value = val.split('-')[0];
lastName.value = val.split('-')[1];
}
})
与watch配置功能一致
监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次
通过配置deep为true, 来指定深度监视
watch(firstName, (nval, oval) => {
fullName.value = nval + '-' + lastName.value
}, { immediate: true, deep: true })
watch(lastName, (nval, oval) => {
fullName.value = firstName.value + '-' + nval;
}, { immediate: true })
import {
reactive,
ref,
computed,
watch,
watchEffect
} from 'vue'
export default {
setup () {
const user = reactive({
firstName: 'A',
lastName: 'B'
})
// 只有getter的计算属性
const fullName1 = computed(() => {
console.log('fullName1')
return user.firstName + '-' + user.lastName
})
// 有getter与setter的计算属性
const fullName2 = computed({
get () {
console.log('fullName2 get')
return user.firstName + '-' + user.lastName
},
set (value: string) {
console.log('fullName2 set')
const names = value.split('-')
user.firstName = names[0]
user.lastName = names[1]
}
})
const fullName3 = ref('')
/*
watchEffect: 监视所有回调中使用的数据
*/
/*
watchEffect(() => {
console.log('watchEffect')
fullName3.value = user.firstName + '-' + user.lastName
})
*/
/*
使用watch的2个特性:
深度监视
初始化立即执行
*/
watch(user, () => {
fullName3.value = user.firstName + '-' + user.lastName
}, {
immediate: true, // 是否初始化立即执行一次, 默认是false
deep: true, // 是否是深度监视, 默认是false
})
/*
watch一个数据
默认在数据发生改变时执行回调
*/
watch(fullName3, (value) => {
console.log('watch')
const names = value.split('-')
user.firstName = names[0]
user.lastName = names[1]
})
/*
watch多个数据:
使用数组来指定
如果是ref对象, 直接指定
如果是reactive对象中的属性, 必须通过函数来指定
*/
watch([() => user.firstName, () => user.lastName, fullName3], (values) => {
console.log('监视多个数据', values)
})
return {
user,
fullName1,
fullName2,
fullName3
}
}
}
基本数据类型
ref对象监听的是ref对象的.value属性(这个代理对象),正确
不能直接监听 ref对象.value(具体值不行)错误
const text = ref('我爱你')
//问: 参数一放了个什么东西?
// 参数一,放到是一个 包裹了基本数据类型的 ref 对象
watch(text, (nval) => {
console.log(nval)
}, { immediate: true })
// 问: 参数一放了个什么东西?
// 参数一不能放字符串
// 这里的参数一 text.value 就相当于直接放了一个 "我爱你字符串"
watch(text.value, (nval) => {
console.log(nval)
}, { immediate: true })
引用数据类型
watch监听obj对象(监听了个ref对象),ref对象监听的是ref对象的.value属性,地址不发生变化监听不到
watch监听obj.value对象(监听了个reactive对象),此时属性发生变化的时候可以监听到
watch监听obj.value.xxx(监听了个reactive对象下的具体属性),直接监听了个字符串,不能直接监听
总结: 在没有开启深度监听的情况下,watch监听的是当前对象的下一层属性(ref对象监听的是.value,reactive对象监听的是属性)
如何理解:监听ref对象的时候,ref.value这个值变化可以监听到,监听ref.value的时候,基本数据类型无法监听,引用数据类型监听的是reactive对象,属性变化可以监听到
开启深度监听会发生什么?监听ref的时候,当开启深度监听的,既可以监听到 .value中地址的变化,也可以监听到对象的属性变化
发现:监听的时候,新值和旧值都是 reactive 对象,想要看到的是属性字符串的新值和旧值,这个怎么办?
参数一变成一个函数(这个函数叫做getter函数),函数的返回值必须是想要监听的属性,此时可以直接获取到字符串的变化
watch(() => obj.value.name, (nval, oval) => {
console.log('监听的是函数 -> ', nval, oval)
})
第一个参数可以写成数组,只要数组中的值发生变化就可以监听到
watch([firstName, lastName, age, hobby], (nval, oval) => {
result.value = `我叫 ${ nval[0] }-${ nval[1] } ,今年 ${ nval[2] } 岁,爱好 ${ nval[3] }`
})
假设监听值有10个,数组中写的值也得有10个,这样数组太长了,也不好用?怎么办? watchEffect 解决
watchEffect - 上来默认就会执行一次处理函数
watchEffect直接写一个监听得处理函数,只要这个函数中用得到值发生变化就可以监听到,有点像computed
watchEffect(() => {
// 注意: watchEffect 是同步追踪依赖的,只有在第一次执行的时候可以异步
result.value = `我叫 ${ firstName.value }-${ lastName.value } ,今年 ${ age.value } 岁,爱好 ${ hobby.value }`
})
setup -- 最早执行,获取不到this
onBeforeMount
onMounted - 异步,发请求...等
onBeforeUpdate - 数据改变,DOM(DOM没有发生变化)更新前会触发更新
onUpdated - 数据改变,DOM(DOM发生改变)更新后会触发更新
onBeforeUnmount
onUnmounted
beforeCreate -> 使用 setup()
created -> 使用 setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
errorCaptured -> onErrorCaptured
卸载
获取到实例调用 app.unmount(); app是整个vue实例,也可以是组件实例
组件标签v-if为false隐藏组件得时候,也会触发卸载
和vue2的差异: vue3中的卸载直接页面组件都没了,页面直接看不到了
vue2的销毁页面还在,只是我们创建的vm实例和页面的DOM间的关联被斩断的
封装获取鼠标实时位置
import { ref, onMounted, onUnmounted } from 'vue'
// 需求:将获取鼠标实时位置的功能封装成一个hook函数
// 这里单独的功能封装到一个hook函数中,把需要的结果return出去
// 在需要使用这个功能的时候引入这个函数即可
// 自定义hook函数用来替代之前vue2当中的mixin
const getMouseLocation = () => {
const pageX = ref(0);
const pageY = ref(0);
const mouseMove = (e) => {
// 解构,解构出pageX然后给pageX起了一个别名为x
const { pageX: x, pageY: y } = e;
pageX.value = x;
pageY.value = y;
}
// 挂载
onMounted(() => {
document.addEventListener('mousemove', mouseMove)
})
// 销毁 - 解绑事件
onUnmounted(() => {
document.removeEventListener('mousemove', mouseMove)
})
return {
pageX,
pageY
}
}
export default getMouseLocation
使用
import mouseLocation from './mouseLocation.js'
const { pageX, pageY } = mouseLocation()
获取页面中的元素步骤
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<hr>
<input type="text" ref="inputRef">
<br>
<button @click="inputFocus">点击按钮让input聚焦</button>
</div>
</template>
<script>
// #region
// #endregion
import { ref } from 'vue'
export default {
name: 'HelloWorld',
props: {
msg: String
},
setup() {
// 注意: 获取页面中DOM元素
// 1. 给元素设置 ref 属性
// 2. setup中return中一定要有一个和 元素的ref名字一样的属性,就可以拿到了
const abc = ref()
const inputFocus = () => {
abc.value.focus(
}
return {
inputRef: abc,
inputFocus
}
},
}
</script>
把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref
应用: 当从合成函数返回响应式对象时,toRefs 非常有用,这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用
问题: reactive 对象取出的所有属性值都是非响应式的
解决: 利用 toRefs 可以将一个响应式 reactive 对象的所有原始属性转换为响应式的 ref 属性
<template>
<h2>App</h2>
<h3>foo: {{foo}}</h3>
<h3>bar: {{bar}}</h3>
<h3>foo2: {{foo2}}</h3>
<h3>bar2: {{bar2}}</h3>
</template>
<script lang="ts">
import { reactive, toRefs } from 'vue'
/*
toRefs:
将响应式对象中所有属性包装为ref对象, 并返回包含这些ref对象的普通对象
应用: 当从合成函数返回响应式对象时,toRefs 非常有用,
这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用
*/
export default {
setup () {
const state = reactive({
foo: 'a',
bar: 'b',
})
const stateAsRefs = toRefs(state)
setTimeout(() => {
state.foo += '++'
state.bar += '++'
}, 2000);
const {foo2, bar2} = useReatureX()
return {
// ...state,
...stateAsRefs,
foo2,
bar2
}
},
}
function useReatureX() {
const state = reactive({
foo2: 'a',
bar2: 'b',
})
setTimeout(() => {
state.foo2 += '++'
state.bar2 += '++'
}, 2000);
return toRefs(state)
}
</script>
核心:
对象: 通过defineProperty对对象的已有属性值的读取和修改进行劫持(监视/拦截)
数组: 通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持
Object.defineProperty(data, 'count', {
get () {},
set () {}
})
问题
对象直接新添加的属性或删除已有属性, 界面不会自动更新
直接通过下标替换元素或更新length, 界面不会自动更新 arr[1] = {}
<script>
// 数据代理: 将data配置项中的数据代理到当前vm实例上
// 数据劫持: 给data中的数据设置响应式,可以拦截到设置值和获取值的过程,从而更新页面
function Vue(options) {
let _data = options.data // _data就是配置项的中的data(拿的是一个地址)
this._data = _data;
// Object.keys(_data) // -> 得到一个属性名组成的数组 -> ['msg', 'text']
// 数据代理
Object.keys(_data).forEach(key => { // 第一次key是 'msg' 第二次key是 'text'
// 第一次key是'msg',给this(当前实例加了'msg'属性)
// 第二次key是 'text',给this(当前实例加了'text'属性)
Object.defineProperty(this, key, {
get() {
console.log('读取vm属性')
return _data[key]
},
set(val) {
console.log('设置vm属性')
_data[key] = val
}
})
})
// 数据劫持
// Object.keys(_data) // --> ['msg', 'text']
Object.keys(_data).forEach(key => {
// 调用数据劫持的函数
// 循环第一次 配置项data 'msg' '我爱你'
// 循环第一次 配置项data 'text' '你是个好人'
defineReactive(_data, key, _data[key])
})
}
// 调用该函数调用了两次,注意: 参数一data拿到的就是配置项中data的地址
// data 'msg' '我爱你'
// data 'text' '你是个好人'
function defineReactive(data, key, value) {
Object.defineProperty(data, key, {
get() {
return value
},
set(val) {
value = val
// 拦截到设置数据的过程,拦截到设置data中属性的过程了
// 这里应该写更页面代码
}
})
}
let vm = new Vue({
el: '#app',
data: {
msg: '我爱你',
text: '你是个好人'
}
})
console.log(vm._data)
console.log(vm.msg) // ---> '我爱你'
vm.msg = '我们不合适'
</script>
vue3中使用了两种代理方式:Proxy(代理)和 Object.defineProperty()
核心:
通过Proxy(代理): 拦截对data任意属性的任意(13种)操作, 包括属性值的读写, 属性的添加, 属性的删除等…
Proxy是js内置的一个类,通过new Proxy可以生成一个代理对象
参数一: 元对象
参数二: 配置对象
通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作
文档:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
ref
ref 对象有一个.value属性,能不能看一眼.value属性长什么样子?
ref 对象的value属性是一个 (…) 这个样子
什么时候会出现这个 (…) 呢?
只有在 Object.defineProperty() 这个方法设置get和set的时候才会出现
所以 ref.value 的底层实现用的 Object.defineProperty()
new Proxy(data, {
// 拦截读取属性值
get (target, prop) {
return Reflect.get(target, prop)
},
// 拦截设置属性值或添加新属性
set (target, prop, value) {
return Reflect.set(target, prop, value)
},
// 拦截删除属性
deleteProperty (target, prop) {
return Reflect.deleteProperty(target, prop)
}
})
proxy.name = 'tom'
<!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是被代理对象
后面所有的操作都是通过代理对象来操作被代理对象内部属性
*/
const proxyUser = new Proxy(user, {
get(target, prop) {
console.log('劫持get()', prop)
// return target[property]
return Reflect.get(target, prop)
},
set(target, prop, val) {
console.log('劫持set()', prop, val)
// target[property] = value
return Reflect.set(target, prop, val); // (2)
},
deleteProperty (target, prop) {
console.log('劫持delete属性', prop)
// return delete target[property]
return Reflect.deleteProperty(target, prop)
},
// 拦截in操作
has(target, property) {
// return property in target
return Reflect.has(target, property)
},
// 拦截 Object.keys()
ownKeys(target) {
// return Object.keys(target)
return Reflect.ownKeys(target)
}
});
// 读取属性值
console.log(proxyUser===user)
console.log(proxyUser.name, proxyUser.age)
// 设置属性值
proxyUser.name = 'bob'
proxyUser.age = 13
console.log(user)
// 添加属性
proxyUser.sex = '男'
console.log(user)
// 删除属性
delete proxyUser.sex
console.log(user)
//has属性
console.log('name' in p1)
console.log('hobby' in p1)
//ownKeys属性
console.log( Object.keys(p1) )
</script>
</body>
</html>
//in 在对象中判断是不是有该属性
//in 在数组中判断是不是有该下标
let obj2 = {
name: '张三',
age: 20
}
console.log('name' in obj2)//true
console.log('age' in obj2)//true
console.log('height' in obj2)//false
console.log('weight' in obj2)//false
console.log('hobby' in obj2)//false
let arr2 = ['我爱你', '你是个好人']
console.log(0 in arr2)//true
console.log(1 in arr2)//true
console.log(2 in arr2)//false
console.log(3 in arr2)//false