Vue3中的composition API的知识总结(二)

上篇由于时间太晚了,就没有写了,这篇接着上篇的续写, Vue3中的composition API的知识总结(一)
菜鸡的码农还是要拼命的奋斗啊。。

toRefs()

将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref

见名知意:· 把什么转化成ref对象

目的:

所以在,开发过程中,我们有时候会对对象进行结构

const obj = reactive({name: 'james', age: '21'})
let { name, age } = obj

//但是这样的结构是,name 和 age 都变成了普通的对象, 就不在是响应式的了

基本使用:

import { toRefs } from 'vue'

setup() {
      const info = reactive<InfoType>({name: 'james', age: 12})
      const {name, age} = toRefs(info)

      const btn = () => {
        // ref 和 reactive之间建立连接,所以下面两句是等价的,都是修改同一个地址中的值
        info.age++
        age.value++
      }
      
      return {
        age,
        btn
      }
  }

解释:

  • 上面的代码中,age的默认值为12,但是当执行之后,age的值变为14
  • toRefsrefreactive之间建立连接,执行同一个地址

源码:

function toRefs(object) {
    //提示必须是一个reactive对象
    if (!isProxy(object)) {
        console.warn(`toRefs() expects a reactive object but received a plain one.`);
    }
    const ret = shared.isArray(object) ? new Array(object.length) : {};
    for (const key in object) {
        //toRefs 就是通过toRef 循环实现的
        ret[key] = toRef(object, key);
    }
    return ret;
}

通过源码分析, toRefs就是对reactive对象进行循环调用toRef()

看下面的解释的toRef函数

toRef()

可以用来为源响应式对象上的某个 property 新创建一个 ref。然后,ref 可以被传递,它会保持对其源 property 的响应式连接。

基本使用:

//toRef  两个参数
//第一个参数: reactive对象
//第二个参数: 转化为响应式的属性名

const state = reactive({
  foo: 1,
  bar: 2
})

const fooRef = toRef(state, 'foo')

fooRef.value++
console.log(state.foo) // 2

state.foo++
console.log(fooRef.value) // 3

源码:

function toRef(object, key) {
    return isRef(object[key])
        ? object[key]
        : new ObjectRefImpl(object, key);  //新建一个响应式属性
}

unRef()

  • 如果参数是一个 ref,则返回内部值,否则返回参数本身。

  • 这是 val = isRef(val) ? val.value : val 的语法糖函数。

isRef()

检查值是否为一个 ref 对象。

customRef()

创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收 tracktrigger 函数作为参数,并且应该返回一个带有 getset 的对象。

Refs | Vue.js (vuejs.org)

shallowRef()

创建一个跟踪自身 .value 变化的 ref,但不会使其值也变成响应式的。

就是如果ref传递的一个复杂数据类型,不会对复杂数据类型进行监听。

基本使用:

//shallowRef  浅层监听
const info = shallowRef({name: 'james'})

const changeAge = () => {
    info.value.name = 'kobe'  //这里深度修改了name的值,不在在UI上更新
    triggerRef(info)   //triggerRef就是用来强制跟新shallowRef的
}

triggerRef()

与shallowRef()配套使用

shallowRef()不让界面更新,triggerRef()就是强制让页面更新。

computed()

跟Options API的computed属性是一样的效果,就是进行一些数据进行,计算转变。

基本使用:

这里没有使用computed函数,那么fullName就不是响应式的

setup() {
    const firstName = ref('12')
    const lastName = ref('34')
    
    //这里firstName和lastName都是响应式的,但是fullName不是响应式的,不能这样写
    const fullName = firstName + lastName
}

使用computed函数, 参数可以传递一个函数,或则对象的形式

函数的形式:

 const firstName = ref('lebron')
 const lastName = ref('james')

 const fullName = computed(() => firstName.value +"_"+ lastName.value)

对象形式:

const fullName = computed({
    get: () => firstName.value + '_' + lastName.value,
    set: (newValue) => {
        //改变值,修改逻辑
    }
})

watchEffect()

在响应式地跟踪其依赖项时立即运行一个函数,并在更改依赖项时重新运行它。

特点:

  • 立即会执行一次

  • 自动收集依赖

    const name = ref('jamse')
    const age = ref(12)
    
    //这个函数会最开始执行一次的,目的存在也是收集依赖,放到一个数组中
    watchEffect(() => {
        console.log(name.value)
    })
    

    上面的watchEffect中,使用name属性,那么就收集了name, 当name发生改变时,就会执行, 改变age,就不会执行,因为age没有在收集的依赖中

类型声明:

interface WatchEffectOptions {
  flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}

interface DebuggerEvent {
  effect: ReactiveEffect
  target: any
  type: OperationTypes
  key: string | symbol | undefined
}

type InvalidateCbRegistrator = (invalidate: () => void) => void

type StopHandle = () => void

function watchEffect(
  effect: (onInvalidate: InvalidateCbRegistrator) => void,
  options?: WatchEffectOptions
): StopHandle

分析上面的定义类型:

  • StopHandle: watchEffect函数的返回值是 返回一个停止监听函数

    //watchEffect返回一个停止侦听的函数
    const stop = watchEffect(() => {
        console.log(age.value)
    })
    
    //上面我是监听的age响应式数据
    //如果当age到了25岁时,就不在侦听了
    if(age.value >=25) {
        stop()   //执行stop函数,就停止侦听
    }
    
  • onInvalidate: 清除副作用

    假如,我们在这个函数里面发送网络请求,当age改变一次,发送一次,当age改变的过快时,上一个请求就没执行完,又执行下一次了,所以,这里我们就需要清除上一次的副作用

    在我们给watchEffect传入的函数被回调时,其实我们可以可以获取到一个参数: onInvalidata

    watchEffect((onInvalidata) => {
        //onInvalidata就是一个清除副作用的函数
        //onInvalidata每次都会执行一次
        
        const timer = setTimeout(() => {
            console.log('网络请求')
        }, 1000)
        onInvalidata(() => {
            //这里面进行相应的副作用操作
            clearTimeout(timer)
        })
        console.log(age.value)
    })
    
  • WatchEffectOptions: 配置项,用的时候,再去查看,这里就不深究了(其实,是我不知道,还没有用到过)

watch()

跟Options API的特性是一样的。

特点:

  • 惰性地执行副作用;
  • 更具体地说明应触发侦听器重新运行的状态;
  • 访问被侦听状态的先前值和当前值。

监听单个数据源

写法一: 传入一个getters函数

const info = reactive({name: 'james', age: 12})
//当监听某一个属性的时候 (监听info.name的改变)
watch(() => info.name, (newValue, oldValue) => {
    console.log(newValue, oldValue)
})

写法二: 传入一个可响应式对象: reactive 和 ref(比较常见ref)

情况一: reactive对象获取到的newValueoldValue本身都是reactive对象

setup() {
      const info = reactive({name: 'james', age: 12})
	
      //直接监听reactive本身
      watch(info, (newValue, oldValue) => {
        console.log(newValue, oldValue);
          //newValue, oldValue的值是一样的,都是指向同一个地址
        
      })
      
      const btn = () => {   //执行btn的时候,会触发watch函数
        info.name = 'kobe'
        info.age = 13
      }


      return {
        ...toRefs(info),
        btn
      }
  }

情况二: 监听reactive的普通对象(应该很少使用吧)

//如果我们想拿到普通对象
watch(() => {
    return {...info}
}, (newValue, oldValue) => {
    console.log(newValue, oldValue)
    //newValue, oldValue 都是普通对象
})

情况三: ref对象获取到的newValue和oldValue都是value值的本身

setup() {
      const info2 = ref('james')

      watch(info2, (newValue, oldValue) => {
          //拿到的都是value值
        console.log(newValue, oldValue);  // kobe   james
      })
      
      const btn = () => {
        info2.value = 'kobe'
      }

      return {
        info2,
        btn
      }
  }

监听多个数据源

传递一个数组,源码内部自己遍历,在判断是reactive或则是ref

const info = reactive({name: 'james'})
const name = ref('21')

watch([info, name], (newValue, oldValue) => {
    console.log(newValue, oldValue)
    //newValue, oldValue都是数组
})

深度侦听

特点:

  • 对于reactive响应式数据,默认就有深度侦听

  • 但是对于结构reactive后的对象,是没有深度侦听的, 需要配置deep属性

setup() {
      const info = reactive({name: 'james', age: 12, kk: {name: 'kk'}})
      const info2 = ref('james')

      watch([info, info2], (newValue, oldValue) => {
        console.log(newValue, oldValue);
      }, {
        immediate: true,     //理解执行
        // deep: true        //深度监听
      })
      
      const btn = () => {
        // info.name = 'kobe'
        // info.age = 13
        info.kk.name = 'BB'
        // info2.value = 'kobe'
      }


      return {
        ...toRefs(info),
        info2,
        btn
      }
  }

针对于普通对象

const info = reactive({name: 'james'})
const name = ref('21')

watch(() => {
    return {...info}    //普通对象
}, (newValue, oldValue) => {
    console.log(newValue, oldValue)
}, {
    deep: true,        //深度侦听
    immediate: true   //立即执行
})

生命周期函数

在这里插入图片描述

因为 setup 是围绕 beforeCreatecreated 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。

在setup中多次使用同一个生命周期,会进行合并。

生命钩子函数接受一个函数作为参数

setup() {
      onMounted(() => {
        console.log('第一个onMounted执行');
      })
      onMounted(() => {
        console.log('第二个onMounted执行');
      })
  }

provide() 和 inject()

基本用法:

  • provide: 提供共享数据

    • provide 函数允许你通过两个参数定义 property:
      1. name (<String> 类型)
      2. value
  • inject: 注入共享数据

    • inject 函数有两个参数:
      1. 要 inject 的 property 的 name
      2. 默认值 (可选)

案例演示:

父组件

//Father.vue
import { provide, ref } from 'vue'
export default {
    setup() {
        let name = ref('james')
        let count = ref(1)
        provide('name', name) //第一个参数标识符, 第二个参数为value值
        provide('count', count)
    }
}

子组件

//Son.vue
import { inject } from 'vue'
export default {
    setup() {
        const name = inject ('name', 'kobe')  //第二个参数为默认值,如果父组件没有提供
    	const count = inject('count')
    }
    //这里的count也是一个ref对象
    //警告:千万不要在子组件修改父组件提供的值(必须要符合单向数据流)
    //原因:如果多个子组件只用的话,就是兄弟之间相互修改了(编程之大忌)
}

优化父组件

使用readonly包裹数据,修改的时候,就会直接报错

import { provide, ref, readonly } from 'vue'
export default {
    setup() {
        let name = ref('james')
        let count = ref(1)
        provide('name', readonly(name)) //第一个参数标识符, 第二个参数为value值
        provide('count', readonly(count))
    }
}

ref拿取DOM

在Vue2中,我们就是通过ref来拿取DOM节点,那么Vue3该如何拿取节点呢? 还是通过ref函数来获取

代码示例:

setup() {
    const titleRef = ref<HTMLDivElement>()
    const btn = () => {
        console.log(titleRef.value)
    }
    return {
        titleRef,
        btn
    }
}

这里还可以使用watchEffect()

 watchEffect(() => {
    console.log(titleRef.value);
    //这样就拿到div这个节点
}, {  //flush有三个属性值
    flush: "post"   //默认的时候,延后执行,改变的时候在执行
    //默认值为pre    //默认会立即执行,当改变的时候,也会执行
    //还有一个值为sync   同步触发(少用,不推荐,低效)
})

总结

写到这里,我感觉Vue3有太多的知识了,现在只是随便笔记,看下官网,就花费了我两天的时间,还有很多的新特性没有总结到。如果以后,使用到了,在补充吧。现在的我,对Vue3的新知识,还不是很熟练的掌握,需要长期的练习,加油吧!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值