setup
setup基础用法
- 新的option,所有的组合API函数都在使用,只在初始化时执行一次
- 函数如果返回对象.对象中的属性或方法,模板中可以直接使用
他是组合API中第一个要使用的函数,是组合API的入口函数
setup
函数会在beforeCreate
、created
之前执行,vue3
也是取消了这两个钩子,统一用setup
代替, 该函数相当于一个生命周期函数,vue
中过去的data
,methods
,watch
等全部都用对应的新增api
写在setup()
函数中
setup(){
const number:number = 120
return{
number
}
}
setup 细节
-
setup执行的时机
- 在beforeCreate之前执行(一次),此时组件对象还没有创建
- this是undefined,不能通过this来访问data/computed/methods/props。(setup在执行的时候,当前组件还没被创建出来,也就意味着:组件实例对象this根本就不能用
- 其实所有的composition API相关回调函数中也都不可以
-
setup的返回值
- 一般都返回一个对象:为模板提供数据,也就是模板中可以直接使用此对象中的所有属性/方法。
- 返回对象中的属性会与data函数返回对象的属性合并成为组件对象的属性
- 返回对象中的方法会与methods中的方法合并成功组件对象的方法
- 如果有重名, setup优先
- 注意:
- 一般不要混合使用: methods中可以访问setup提供的属性和方法,但在setup方法中不能访问data和methods
- setup不能是一个async函数:因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性数据
-
setup的参数
- setup(props, context) / setup(props, {attrs,slots, emit,expose))
- props:包含props配置声明且传入了的所有属性的对象
- attrs:包含没有在props配置中声明的属性的对象,相当于this.$attrs(获取当前组件标签上的所有属性的对象,都是该属性是在props中没有声明接收的所有的尚须经的对象)
- slots:包含所有传入的插槽内容的对象,相当于this.$slots
- emit:用来分发自定义事件的函数,相当于this.$emit
- expose:暴露公共的函数
//父组件
<div @xxx="xxx"></div>
setup(){
function xxx(text:string){
}
return{
xxx
}
}
//子组件
setup(){
function emitXXX(){
context.xxx = 'sss'
}
return{
emitXXX
}
}
Ref && Reactive
ref
- 作用:定义一个数据的响应式
- 语法: const xxx = ref(initValue);
- 创建一个包含响应式数据的引用(reference)对象
- js中操作数据: xxx.value
- 模板中操作数据:不需要.value
- 一般用来定义一个基本类型的响应式数据
要记得引入ref
import { defineComponent, Ref, ref } from 'vue';
setup(){
let count:Ref<number> = ref(0)//将数据变成响应式的
function updateCount():void{
count.value++
}
return{
count,
updateCount
}
}
ref一般用于定义一个基本类型的响应式数据
reactive
- 作用:定义多个数据的响应式
- const proxy = reactive(obj):接收一个普通对象然后返回该普通对象的响应式代理器对象,只能对响应式代理器对象操作
- 响应式转换是深层的:会影响刘象内部所有嵌套的属性
- 内部基于ES6 的Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
/***
user对象==》代理对象
obj对象==》目标对象
*/
setup(){
const obj = {
name:'小明',
age:20,
wife:{
name:'hh',
age:18,
cars:['ben','bao']
}
}
const user = reactive(obj)
function update():void{
user.name += '=='
}
return{
user,
update
}
}
修改响应式对象,页面才会进行更新。因为修改响应式对象才会触发Proxy和Reflect的方法
reactive & ref 的细节
- 是Vue3的composition API中2个最重要的响应式API
- ref用来处理基本类型数据, reactive用来处理对象(递归深度响应式)
- 如果用ref对象/数组,内部会自动将对象/数组转换为reactive的代理对象
- ref内部:通过给value属性添加getter/setter来实现对数据的劫持
- reactive内部:通过使用Proxy来实现对对象内部所有数据的劫持,并通过Reflect操作对象内部数据
- ref的数据操作:在js中要.value,在模板中不需要(内部解析模板时会自动添加.value)
ref沿用vue2方法
获取DOM节点
const inputRef = ref<HTMLElement|null>(null)
计算属性 & 监视
计算属性
需要从vue
中引入computed
它返回的值是一个ref对象。 里面可以传方法,或者一个对象,对象中包含
set()
、get()
方法
使用方法:
//当只需要使用get方法时
const fun = computed(()=>{
return xxx
})
//当需要使用set和get方法时
const fun = computed({
get(){
}
set(){
}
})
监视
需要从vue
中引入watch
watch('被监视的对象',({'对象中的属性'})=>{
},{immediate:true,deep:true})//一挂载就监视执行一次
/**************************************************/
//下面这种监视不需要配置,会自动封装
watchEffect(()=>{
})
当我们监视的是非响应式的数据时
watch([()=>xxx,()=>yyy],()=>{
})
停止监视
可以使用watch的返回值进行停止监视
watch
的返回值是一个函数,我们叫这个函数为stop
setTimeout(()=>{
stop()
//接下来修改被监听的数据时,不会触发watch
},1000)
vue3的生命周期函数
在vue3中可以直接使用vue2的生命周期函数,但也可以使用vue3的生命周期钩子
在setup
中使用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ku4wibuc-1652526063076)(C:\Users\12274\AppData\Roaming\Typora\typora-user-images\image-20220221183800084.png)]
都需要在vue中引入
自定义hook函数
- 使用Vue3的组合API封装的可复用的功能函数
- 自定义hook的作用类似于vue2中的mixin技术
- 自定义Hook的优势:很清楚复用功能代码的来源,更清楚易懂
- 需求1:收集用户鼠标点击的页面坐标
新建一个hook文件保存下面的代码
import { ref, onMounted, onUnmounted } from 'vue'
export default function useMousePosition ()=>{
//初始化坐标
const x = ref(-1)
const y = ref(-1)
//点击事件的回调函数
const clickHandler = (event) =>{
x.value = event.pageX
y.value = event.pageY
}
//页面已经加载完毕了,在进行点击操作
//页面加载完毕的生命周期
onMouinted(()=>{
window.addEventListener('click',clickHandler)
})
onBeforeUnmonted(()=>{
window.addEventListener('click',clickHandler)
})
return{
x,y
}
}
在vue组件中引用
import useMousePositon from 'xxx'
setup(){
const { x , y } = useMousePosition()
}
toRef
和toRefs
toRef 和 toRefs 可以用来复制 reactive 里面的属性然后转成 ref,而且它既保留了响应式,也保留了引用,也就是你从 reactive 复制过来的属性进行修改后,除了视图会更新,原有 ractive 里面对应的值也会跟着更新,如果你知道 浅拷贝 的话那么这个引用就很好理解了,它复制的其实就是引用 + 响应式 ref
toRef——复制 reactive 里的单个属性并转成 ref
为源响应式对象上的某个属性创建一个ref对象,二者内部操作的是同一个数据值,更新时二者是同步的
ref
和toRef
的区别:
- ref本质是拷贝,修改响应式数据不会影响原始数据;toRef的本质是引用关系,修改响应式数据会影响原始数据
- ref数据发生改变,界面会自动更新;toRef当数据发生改变是,界面不会自动更新
- toRef传参与ref不同;toRef接收两个参数,第一个参数是哪个对象,第二个参数是对象的哪个属性
应用场景:如果想让响应式数据和以前的数据关联起来,并且想在更新响应式数据的时候不更新UI,那么就使用toRef
toRefs——复制 reactive 里的所有属性并转成 ref
把一个响应式对象转换成普通对象,该普通对象的每个property都是一个ref
应用:当从合成函数返回响应式对象时,toRefs非常有用,这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用
问题: reactive对象取出的所有属性值都是非响应式的
解决:利用toRefs可以将一个响应式reactive对象的所有原始属性转换为响应式的ref属性
shallowReactive & shallowRef——浅Rective和浅Ref
shallowReactive :只处理了对象内最外层属性的响应式(也就是浅响应式)
shallowRef:只处理了value的响应式,不进行对象的reactive处理
- 什么时候用浅响应式呢?
- 一般情况下使用ref和reactive即可
- 如果有一个对象数据,结构比较深,但变化时只是外层属性变化===> shallowReactive。
- 如果有一个对象数据,后面会产生新的对象来替换===> shallowRef
readonly & shallowReadonly——只读和浅只读
- readonly:
- 深度只读数据
- 获取一个对象(响应式或纯对象)或ref并返回原始代理的只读代理。
- 只读代理是深层的:访问的任何嵌套property 也是只读的。
- shallowReadonly
- 浅只读数据
- 创建一个代理,使其自身的property为只读,但不执行嵌套对象的深度只读转换
应用场景:在某些特定情况下,我们可能不希望对数据进行更新的操作,那就可以包装生成一个只读代理对象来读取数据,而不能修改或删除
toRaw & markRaw——还原和标记
- toRaw
- 返回由
reactive
或readonly
方法转换成响应式代理的普通对象。
-这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发界面更新。
- 返回由
- markRaw
- 标记一个对象,使其永远不会转换为代理。返回对象本身。
- 应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类实例或Vue 组件对象。
- 当渲染具有不可变数据源的大列表时,跳过代理转换可以提高性能。
customRef——自定义Ref
- 创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显式控制
- 需求:使用customRef 实现debounce的示例
import { defineComponent,ref,customRef } from 'vue'
//自定义防抖函数
//value传入的数据,将来数据的类型不确定,所以用泛型,delay防抖的间隔时间,默认为200毫秒
function useDebounceRef<T>(value:T,delay=200){
//存储一个定时器的变量
let timeOutId : number
return customRef((track,trigger)=>{
return {
get(){
//告诉vue追踪数据
track()
return value
},
set(){
clearTimeout(timeOutId)
//开启定时器
timeOutId = setTimeout(()=>{
value = newValue
//告诉vue更新界面
trigger()
},delay)
}
}
})
}
export default defineComponent({
name:'App',
setup(){
const keyword = useDebounceRef('abc',500)
return {
keyword
}
}
})
provide & inject
- provide和inject提供依赖注入,功能类似2.x的provide/inject
- 实现跨层级组件(祖孙)间通信
//父组件
export default defineComponent({
name:'app',
setup(){
const color = ref('red')
provide('color',color)
return {
color
}
}
})
//孙子组件
const color = inject('color')
响应式判断
- isRef:检查一个值是否为一个ref对象
- isReactive:检查一个对象是否是由reactive创建的响应式代理
- isReadonly:检查一个对象是否是由readonly创建的只读代理
- isProxy:.检查一个对象是否是由reactive或者readonly方法创建的代理
主要内容实现源码
shallowReactive & reactive 的实现代码
实现劫持、监视、响应数据
const reactiveHandler = {
//获取属性值
get(target, prop){
if(prop === "_is_reactive") return true
return Reflect.get(target,prop)
}
//修改属性或者添加属性
set(target,prop,val){
return Reflect.set(target,prop,val)
}
//删除某个属性
deleteProperty(target,prop){
return Reflect.deleteProperty(target,prop)
}
}
//定义一个shallowReactive函数,传入一个目标对象
function shallowReactive(target){
//判断当前的目标是不是一个object类型(数组/对象)
if(target&&typeof target === 'object'){
return new Proxy(target,reactiveHandler)
}
//如果传入的数据是基本数据类型,那么就直接返回
return target
}
//定义一个reactive函数,传入一个目标对象
function reactive(target){
//判断当前的目标是不是一个object类型(数组/对象)
if(target&&typeof target === 'object'){
//对数或者是对象中的所有数据进行reactive的递归处理
//先判断当前的数据是不是数组
if(Array.isArray(target)){
//数组的数据要进行遍历操作
target.forEach((item,index)=>{
target[index] = reactive(item)
})
}else{
//再判断当前数据是不是对象
//对象的数据也要进行遍历操作
Object.keys(target).forEach(key=>{
target[key] = reactive(target[key])
})
}
//先判断当前的数据是不是对象
return new Proxy(target,reactiveHandler)
}
//如果传入的数据是基本数据类型,那么就直接返回
return target
}
shallowReadonly & readonly 的实现代码
const readonlyHandler = {
//获取属性值
get(target, prop){
if(prop === "_is_readonly") return true
return Reflect.get(target,prop)
}
//修改属性或者添加属性
set(target,prop,val){
console.warn('只能读取数据')
return 1
}
//删除某个属性
deleteProperty(target,prop){
console.warn('只能读取数据')
return 1
}
}
function shallowReadonly(target){
//需要判断当前的数据是不是对象
if(target&&typeof target === 'object'){
return new Proxy(target,readonlyHandler)
}
//如果不是对象
return target
}
function readonly(target){
//需要判断当前的数据是不是对象
if(target&&typeof target === 'object'){
//先判断当前的数据是不是数组
if(Array.isArray(target)){
//数组的数据要进行遍历操作
target.forEach((item,index)=>{
target[index] = readonly(item)
})
}else{
//再判断当前数据是不是对象
//对象的数据也要进行遍历操作
Object.keys(target).forEach(key=>{
target[key] = readonly(target[key])
})
}
return new Proxy(target,readonlyHandler)
}
//如果不是对象
return target
}
shallowRef & ref 的实现代码
function shallowRef(target){
return {
//保存target数据
_value:target,
get value(){
return this._value
},
set value(val){
this._value = val
}
}
}
function ref(target){
target = reactive(target)
return {
_is_ref:true//标识当前的对象是ref对象
//保存target数据
_value:target,
get value(){
return this._value
},
set value(val){
this._value = val
}
}
}
isRef & isReactive & isReadonly的实现代码
function isRef(obj){
return obj && obj._is_ref
}
function isReactive(obj){
return obj && obj._is_reactive
}
function isRef(obj){
return obj && obj._is_readonly
}
alue(val){
this._value = val
}
}
}
function ref(target){
target = reactive(target)
return {
_is_ref:true//标识当前的对象是ref对象
//保存target数据
_value:target,
get value(){
return this._value
},
set value(val){
this._value = val
}
}
}
### isRef & isReactive & isReadonly的实现代码
```tsx
function isRef(obj){
return obj && obj._is_ref
}
function isReactive(obj){
return obj && obj._is_reactive
}
function isRef(obj){
return obj && obj._is_readonly
}