Vue3 组合式API(setup、ref、reactive、computed、watch、hook、shallow、provide )详解

Vue3 组合式API(setup、ref、reactive、computed、watch、hook、shallow、provide )等详解

  • 1、拉开序幕的setup
    • 1.1、For Example
    • 1.2、:bookmark: 注意点
  • 2、响应式数据ref函数
    • 2.1、For Example
    • 2.2、:bookmark: ref是怎么做到的响应式
  • 3、响应式数据reactive函数
    • 3.1、For Example
    • 3.2、:bookmark: ref与reactive的区别
  • 4、计算属性computed
    • 4.1、For Example
  • 5、监听器watch
    • 5.1、监听单个属性变化
    • 5.2、监听多个属性变化
    • 5.3、监听reactive对象
    • 5.4、监听reactive对象下的属性
    • 5.4、监听reactive对象下的对象
    • 5.5、watchEffect
  • 6、自定义hook函数
    • 6.1、For Example
  • 7、toRef
  • 8、shallowReactive 与 shallowRef
  • 9、readonly 与 shallowReadonly
  • 10、toRaw 与 markRaw
  • 11、customRef
  • 12、provide 与 inject

1、拉开序幕的setup

说明:Vue3.0中一个新的配置项,值为一个函数。

在Vue3中,我们使用setup函数来编写组件逻辑。setup函数是Vue3中的一个新概念,它是一个在组件实例创建之前被调用的函数。它接收两个参数:props和context。props是组件的属性,而context则是一些实用的工具和API。

1.1、For Example

<template>
  <div>
    <p>{{ message }}</p>
    {{ say() }}
  </div>
</template>

<script>
export default {
  name: 'MyComponent',
  setup(props) {
    const message = 'Hello, Vue3!'
	function say(){
		console.log(`message ===== ${message}`)
	}
    return {
      message,
      say
    }
  }
}
</script>

在这个例子中,创建了message变量与一个say的方法。而后在setup函数中返回一个对象,该对象包含了我们希望在模板中使用的数据。在模板中,我们可以像使用普通的data属性一样使用setup返回的数据。使用setup函数的好处是它使得我们能够更好地组织和封装组件的逻辑。我们可以将组件的逻辑放在一个单独的函数中,使得代码更易于维护和测试。此外,setup函数还可以访问到一些实用的工具和API,例如:reactive、computed、watch等(这些将会在后面说到)。

1.2、 注意点

  • setup函数必须返回一个对象或函数。这个对象或函数包含了组件需要的数据、方法和生命周期钩子等。
  • setup函数只能在组件实例创建之前被调用,所以在setup函数中不能使用this关键字和组件实例的属性和方法。
  • setup函数可以访问到props和context两个参数。props是组件的属性,它是只读的;context包含了一些实用的工具和API,例如:attrs、slots、emit等。
  • 在setup函数中创建响应式数据可以使用Vue3提供的reactive、ref、computed等API。但是,不要在setup函数中使用Vue2的data选项。
  • 在setup函数中使用生命周期钩子可以使用Vue3提供的onXXX函数,例如:onMounted、onUpdated、onUnmounted等。
  • 在setup函数中使用异步代码可以使用Vue3提供的异步函数,例如:watchEffect、onServerPrefetch等。

2、响应式数据ref函数

ref 可以用来创建响应式的数据。它接收一个参数作为初始值,返回一个包含value属性的对象。这个value属性是一个响应式的数据,可以被用于模板中。

2.1、For Example

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="increment"> +1 </button>
  </div>
</template>

<script>
import { ref } from 'vue'
export default {
  name: 'MyComponent',
  setup() {
    const count = ref(0)

    const increment = () => {
      count.value++
    }

    return {
      count,
      increment
    }
  }
}

在这个例子中,我们使用ref函数创建了一个响应式的count变量,并将它作为组件的返回值。在模板中,我们可以像使用普通的变量一样使用count变量,它会自动更新视图。注意,在修改ref变量的值时,需要使用 value 属性。例如,在increment函数中,我们使用 count.value++ 来增加count变量的值。

ref函数还可以用于创建响应式的对象和数组。

const user = ref({
  name: 'John',
  age: 30
})
const list = ref([1, 2, 3])

总而言之,Vue3的ref函数是一个非常有用的API,它可以用于创建响应式的数据,使得组件的状态更易于管理和维护

2.2、 ref是怎么做到的响应式

Vue的ref是通过Proxy实现的响应式。在Vue3中,每个组件实例都有一个Proxy对象,用于拦截对组件数据的读取和修改。当我们使用ref创建一个响应式变量时,Vue会将这个变量包装成一个Proxy对象。这个Proxy对象会拦截对变量的读取和修改,以便在变量发生变化时,能够触发组件的重新渲染。具体来说,当我们读取ref变量的值时,Proxy会返回value属性的值。当我们修改ref变量的值时,Proxy会拦截这个修改,并将新的值赋给value属性。这个过程中,Vue会检测到ref变量的变化,并触发组件的重新渲染。而对于具体是如何实现响应式的可以参考该文:深入底层——浅谈Vue2和Vue3的响应式数据实现原理

3、响应式数据reactive函数

reactive 可以用来创建响应式的数据对象。它接收一个普通的JavaScript对象作为参数,返回一个响应式的Proxy对象。这个Proxy对象会拦截对对象属性的读取和修改,以便在属性发生变化时,能够触发组件的重新渲染。

3.1、For Example

<template>
  <div>
    <p>Name: {{ user.name }}</p>
    <p>Age: {{ user.age }}</p>
    <button @click="incrementAge">Age + 1</button>
  </div>
</template>

<script>
import { reactive } from 'vue'
export default {
  name: 'MyComponent',
  setup() {
    const user = reactive({
      name: 'John',
      age: 30
    })

    const incrementAge = () => {
      user.age++
    }

    return {
      user,
      incrementAge
    }
  }
}

使用reactive函数创建了一个响应式的user对象,并将它作为组件的返回值。在模板中,我们可以像使用普通的对象一样使用user对象,它会自动更新视图。在修改reactive对象的属性时,我们不需要使用value属性。例如,在incrementAge函数中,我们使用user.age++来增加user对象的age属性的值。

3.2、 ref与reactive的区别

ref和reactive都可以做到响应式的数据,那这两者之间有什么区别呢?

  • ref

    • ref是用于创建响应式的基本数据类型(如数字、字符串、布尔值等)的API,它接收一个初始值作为参数,并返回一个带有value属性的对象。这个value属性是一个响应式的Proxy对象,它会拦截对value的读取和修改,并在变化时触发组件的重新渲染。
    • ref通过Object.defineProperty()getset来实现响应式(数据劫持)。
  • reactive

    • reactive是用于创建响应式对象的API,它接收一个普通的JavaScript对象作为参数,并返回一个响应式的Proxy对象。这个Proxy对象会拦截对对象属性的读取和修改,并在变化时触发组件的重新渲染。
    • reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。

4、计算属性computed

Vue3 的计算属性与 Vue2 相似,但也有一些变化。在 Vue3 中,计算属性仍然是一个函数,但使用 computed 将其定义为计算属性时,需要使用 ref 函数包装计算属性的返回值。

4.1、For Example

		import {computed} from 'vue'
		
        // 单向响应式
        person.fullName = computed(() => {
            return person.start + ' ' + person.end
        })
        // 完整响应式
        person.fullName = computed({
            get() {
                return person.start + ' ' + person.end
            },
            set(val) {
                const arr = val.split(' ')
                person.start = arr[0]
                person.end = arr[1]
            }
        })

可以看到在Vue3当中的computed计算属性是加在setup当中,并且需要从vue当中解构引入这个计算属性

5、监听器watch

Vue3的watch和Vue2的watch相似,但是还是存在一点变化,需要从vue当中解构出watch,并且watch也是写在setup下的,直接看以下案例:

5.1、监听单个属性变化

        watch(sum, () => {
            console.log('sum change')
        }, {
            immediate: true,
            deep: true
        })

5.2、监听多个属性变化

        watch([sum, msg], (newVal, oldVal) => {
            console.log('change old =', oldVal,"   new =", newVal)
        })

5.3、监听reactive对象

如果是reactive定义的数据,获取不到正确的oldVal

        watch(person, (newVal, oldVal) => {
            console.log('person change old =', oldVal, "   new =", newVal)
        }, {
            // 强制开始了深度监视、配置deep无效
            deep: false
        })

5.4、监听reactive对象下的属性

        watch(() => { return person.age }, (newVal, oldVal) => {
            console.log('person age change old =', oldVal, "   new =", newVal)
        })
        watch(() => { return [person.age, person.fullName] }, (newVal, oldVal) => {
            console.log('person age change old =', oldVal, "   new =", newVal)
        })

5.4、监听reactive对象下的对象

        watch(() => { return person.job }, (newVal, oldVal) => {
            console.log('person job change old =', oldVal, "   new =", newVal)
        }, {
            deep: true
        })

5.5、watchEffect

需要从vue当中解构引入、watchEffect在回调当中使用了那些数据就对那些数据进行监听

        watchEffect(() => {
            console.log("watchEffect", sum.value)
        })

6、自定义hook函数

Vue3中的自定义hook是通过使用函数式组件和Composition API实现的。自定义hook可以将重复的逻辑提取出来,使代码更加简洁和易于维护。要创建一个自定义hook,可以定义一个函数,该函数接受参数并返回一个对象或数组,该对象或数组可供组件使用。

6.1、For Example

filename usePoint.js

import { onBeforeUnmount, onMounted, reactive } from 'vue';
function useSavePoint() {
    let point = reactive({
        x: 0,
        y: 0
    })
    function savePoint(e) {
        console.log(e)
        point.x = e.clientX
        point.y = e.clientY
    }
    onMounted(() => {
        window.addEventListener('click', savePoint)
    })
    onBeforeUnmount(() => {
        window.removeEventListener('click', savePoint)
    })

    return point
}

export default useSavePoint

比如说,我们在一个系统当中需要实现一个功能,如上代码,即监听鼠标的单击事件,获取鼠标的当前位置,这个实现起来还是较为简单的,但是这个小功能其他的人也需要进行使用,我们就可以将这一段代码抽离出来作为一个hook,如上代码所示,使用一个usePoint.js进行保存,而如何使用这个之定义hook呢?如下:

<template>
    <div>
        x ==== >>>> {{ point.x }}
        <br />
        y ==== >>>> {{ point.y }}
    </div>
</template>


<script>
import useSavePoint from './usePoint';

export default {
    name: 'SelfHook',
    setup() {
        let point = useSavePoint()
        return {
            point
        }
    }
}

</script>

Vue3中的自定义hook可以让我们更好地重用逻辑和组合功能,使我们的代码更加简洁和可维护,就和vue2当中的minix混入比较像

7、toRef

创建一个 ref 对象,其value值指向另一个对象中的某个属性。

const name = toRef(person,'name')

在要将响应式对象中的某个属性单独提供给外部使用时可以使用toRef

8、shallowReactive 与 shallowRef

shallowReactive:只处理对象最外层属性的响应式(浅响应式)。 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化事使用

shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换时使用

9、readonly 与 shallowReadonly

当我们读取别的hook或者组件时不希望改变其内部值可以使用

  • readonly: 让一个响应式数据变为只读的(深只读)。
  • shallowReadonly:让一个响应式数据变为只读的(浅只读)。

10、toRaw 与 markRaw

  • toRaw:
    • 作用:将一个由reactive生成的响应式对象转为普通对象
    • 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
  • markRaw:
    • 作用:标记一个对象,使其永远不会再成为响应式对象。
    • 应用场景:
      1. 有些值不应被设置为响应式的,例如复杂的第三方类库等。
      2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。

11、customRef

创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。

<template>
    <input v-model="keyword" />
    <h3>{{ keyword }}</h3>
</template>
<script>
import { customRef } from 'vue';

export default {
    name: 'selfRef',
    setup() {
        // let keyword = ref("1")
        function myRef(value, delay) {
            let timer
            return customRef((track, trigger) => {
                return {
                    get() {
                        console.log('自定义ref读取====>>>>>', value)
                        track()
                        return value
                    },
                    set(newValue) {
                        clearTimeout(timer)
                        timer = setTimeout(() => {
                            console.log('自定义ref修改====>>>>>', newValue)
                            value = newValue
                            trigger()
                        }, delay)
                    }
                }
            })

        }
        let keyword = myRef("1", 20)

        return {
            keyword
        }
    }
}   
</script>

12、provide 与 inject

实现祖与后代组件间通信,父组件有一个 provide 选项来提供数据,后代组件有一个 inject 选项来开始使用这些数据,这个在vue2当中也有使用

	祖先组件
    provide('car', {
      name: '奔驰'
    })
    
    孙(子代)组件
    const car = inject('car')

你可能感兴趣的:(#,vue3,前端,vue3,组合式API)