vue3 新特性--Composition API

一、常用 Composition API

1. setup

setup 是 Vue3.0 中一个新的配置项,值为一个函数,是所有 Composition API 的入口

两个注意点

  • 1.setup 执行的时机:在 beforeCreate 之前执行一次,this 是 undefined
  • 2.setup 的参数:
    • props:值为对象,包含组件外部传递过来,且组件内部声明接收了的属性
    • content:上下文对象
      • attrs:值为对象,包含组件外部传递过来,但没有在 props 配置中声明的属性,相当于 this.$attrs
      • slots:收到的插槽内容,相当于 this.$slots
      • emit:分发自定义事件的函数,相当于 this.$emit

2. ref 和 reactive

2.1 ref 和 reactive 使用
  • ref 用于定义一个响应式的数据
    • 定义基本数据类型:响应式是靠 Object.defineProperty() 的 get 和 set 完成的
    • 定义对象数据类型:内部实际使用 reactive 函数
  • reactive 用于定义一个对象类型的响应式数据
<template>
    <div>num: {{ num }}</div>
    <div>obj.name: {{ obj.name }}</div>
    <div>obj.age: {{ obj.age }}</div>
</template>
import { ref, reactive } from 'vue'
export default {
  setup() {
    const num = ref(1)
    const obj = reactive({
      name: '张三',
      age: 18
    })
    setTimeout(() => {
      num.value ++;
      obj.age ++;
    }, 3000);
    return {
      num,
      obj
    }
  }
}
2.2 ref 与 reactive 对比
  • 从定义数据角度对比:
    • ref用来定义:基本类型数据。
    • reactive用来定义:对象(或数组)类型数据。
    • 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象。
  • 从原理角度对比:
    • ref通过Object.defineProperty()的get与set来实现响应式(数据劫持)。
    • reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
  • 从使用角度对比:
    • ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value。
    • reactive定义的数据:操作数据与读取数据:均不需要.value。
2.3 vue2 和 vue3 响应式对比
Object.defineProperty(data, 'count', {
    get () {}, 
    set () {}
})
  • vue3实现原理:
    • 通过 Proxy(代理):拦截对象中任意属性的变化,包括属性值的读写、属性的添加、属性的删除等
    • 通过 Reflect(反射):对源对象的属性进行操作
const proxy = 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'

3. 计算属性与监视

3.1 computed

与 vue2 中 computed 配置功能一致

import { computed, reactive } from 'vue'
export default {
  setup() {
    const person = reactive{
      firstName: '张',
      lastName: '三'
    }
    // const fullName = computed(() => {
    //   return `${person.firstName}-${person.lastName}`
    // })
    const fullName = computed({
      get() {
        return `${person.firstName}-${person.lastName}`
      },
      set(value) {
        const nameArr = value.split('-')
        person.firstName = nameArr[0]
        person.lastName = nameArr[1]
      }
    })
    return {
      person,
      fullName
    }
  }
}
3.2 watch

与 vue2 中 watch 配置功能一致

注意点:

  1. 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
  2. 监视reactive定义的响应式数据中某个属性时:deep配置有效。
//情况一:监视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配置有效
3.3 watchEffect
  • watchEffect 与 watch 区别:watch 既要指明监视的属性,也要指明监视的回调。而 watchEffect 不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
// watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
watchEffect(()=>{
    const x1 = sum.value
    const x2 = person.age
    console.log('watchEffect配置的回调执行了')
})

4. 生命周期

vue3生命周期

  • vue3 中可以继续使用 vue2 中的生命周期钩子,但是有两个被更名

    • beforeDestroy 更名为 beforeUnmount
    • destroyed 更名为 unmounted
  • vue3 也提供了 Composition API 形式的生命周期钩子,与 vue2 中钩子对应关系如下:

    • beforeCreate ===> setup
    • created =======> setup
    • beforeMount ===> onBeforeMount
    • mounted ======> onMounted
    • beforeUpdate ===> onBeforeUpdate
    • updated =======> onUpdated
    • beforeDestroy ==> onBeforeUnmount
    • destroyed =====> onUnmounted
    • onRenderTracked(新增,调试用)
    • onRenderTriggered(新增,调试用)

vue2和vue3生命周期钩子对比

5. 自定义 hook 函数

  • hook 本质是使用 vue3 的组合 API 封装的可复用的功能函数

  • 示例1:收集用户鼠标点击的页面坐标

import { ref, onMounted, onUnmounted } from 'vue'

export default function useMousePosition () {
  // 初始化坐标数据
  const x = ref(-1)
  const y = ref(-1)

  // 用于收集点击事件坐标的函数
  const updatePosition = (e: MouseEvent) => {
    x.value = e.pageX
    y.value = e.pageY
  }

  // 挂载后绑定点击监听
  onMounted(() => {
    document.addEventListener('click', updatePosition)
  })

  // 卸载前解绑点击监听
  onUnmounted(() => {
    document.removeEventListener('click', updatePosition)
  })

  return {x, y}
}
  • 示例2:使用axios发送异步ajax请求
import { ref } from 'vue'import axios from 'axios'

export default function useUrlLoader<T>(url: string) {

  const result = ref<T | null>(null)
  const loading = ref(true)
  const errorMsg = ref(null)

  axios.get(url)
    .then(response => {
      loading.value = false
      result.value = response.data
    })
    .catch(e => {
      loading.value = false
      errorMsg.value = e.message || '未知错误'
    })

  return {
    loading,
    result,
    errorMsg,
  }
}

6. toRef 和 toRefs

  • toRef 作用:为 reactive 对象上的属性创建 ref。创建的 ref 与源属性同步,修改源属性将更新 ref,修改 ref 也将更新源属性
const state = reactive({
  foo: 1,
  bar: 2
})
const fooRef = toRef(state, 'foo')

// 改变 ref 值,源属性值发生变化
fooRef.value++
console.log(state.foo) // 2

// 修改源属性值,ref 值发生变化
state.foo++
console.log(fooRef.value) // 3

// 错误示范,fooRef 不会与 state.foo 同步,因为 ref() 接收的是一个纯字符串值
// const fooRef = ref(state.foo)
  • toRefs 作用:将 reactive 对象转换为普通对象,普通对象的每个属性都是指向原始对象相应属性的 ref。每个单独的 ref 都是使用 toRef 创建的
function useFeatureX() {
  const state = reactive({
    foo: 1,
    bar: 2
  })
 
  // 返回时转化为 refs
  return toRefs(state)
}
// 对象解构不会丢失响应式
const { foo, bar } = useFeatureX()

二、其他 Composition API

1. shallowReactive 与 shallowRef

  • shallowReactive:只处理对象最外层属性的响应式(浅响应式)
  • shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理
  • 什么时候使用:
    • 如果一个对象结构比较深,但只是外层属性发生变化,则使用 shallowReactive
    • 如果一个对象,后续不会修改其属性,而是用新的对象来替换,则使用 shallowRef

2. readonly 与 shallowReadonly

  • readonly:让一个响应式数据变为只读的(深只读)
  • shallowReadonly:让一个响应式数据变为只读的(浅只读)
  • 使用场景:不希望数据被修改时

3. toRaw 与 markRaw

  • toRaw:将一个由 reactive 生成的响应式对象转为普通对象

  • 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新

  • markRaw:标记一个对象,使其永远不会成为响应式对象

  • 使用场景:

    • 有些值不应被设置为响应式,例如复杂的第三方类库等
    • 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能

4. customRef

  • 作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制
import { ref, customRef } from 'vue'
export default {
    name:'Demo',
    setup(){
        // let keyword = ref('hello') //使用 Vue 准备好的内置 ref
        // 自定义一个 myRef
        function myRef(value, delay){
            let timer
            // 通过 customRef 去实现自定义
            return customRef((track, trigger)=>{
                return{
                    get(){
                        track() // 告诉 vue 这个 value 值是需要被“追踪”的
                        return value
                    },
                    set(newValue){
                        clearTimeout(timer)
                        timer = setTimeout(()=>{
                            value = newValue
                            trigger() // 告诉 vue 去更新界面
                        },delay)
                    }
                }
            })
        }
        // 使用自定义的ref
        let keyword = myRef('hello', 500) 
        return {
            keyword
        }
    }
}

5. provide 与 inject

  • 作用:实现祖与后代组件之间的通信。父组件有一个 provide 选项来提供数据,后代组件有一个 inject 选项来使用这些数据

provide和inject

// 祖组件
setup() {
    let car = reactive({name: '奔驰', price: '40万'})
    provide('car', car)
}

// 后代组件
setup() {
    const car = inject('car')
    return { car }
}

6. 响应式数据的判断

  • isRef:检查一个值是否为 ref 对象
  • isReactive:检查一个对象是否是由 reactive 创建的响应式代理
  • isReadonly:检查一个对象是否是由 readonly 创建的只读代理
  • isProxy:检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

参考文档:
掘金:Vue3.0 新特性以及使用经验总结
Vue3 API toRef

posted @ 2022-03-21 10:49  这货不是古月先生  阅读(509)  评论(0编辑  收藏  举报