文章目录
- 1. 组合Api介绍
- setup
- 2. 响应函数
- 2.1 ref
- 2.2 reactive
- 2.3 toRef和toRefs
- 2.4 readonly
- 2.5 customRef
1. 组合Api介绍
组合Api其实时用于解决功能、数据和业务逻辑分散的问题,使项目更益于模块化开发以及后期维护。
vue2.x — optionsApi 配置式Api — react类组件
vue3.x — compositionApi 组合Api — react函数组件中hook
setup
- 它是vue3新增的一个配置项,值为一个函数,所有的组合式 API 方法都写在 setup函数中。
- 组件中用到的数据,方法 计算属性,事件方法,监听函数,都配置在setup 中。
- setup的函数值为一个函数,该函数的返回值为一个对象,对象中的属性和方法均可以在模板中直接使用。
- setup默认情况下不能使用async/await,在读取数据时它只能是json对象,才能读取到里面的属性值,如果你后续使用了异步组件+Suspense组合,可以使用async/await
- setup中不能使用this
- setup有两个参数,参数1对应就是props属性对象,但是它不能解构,一但解构响应式就失效,参数2为一个context对象,此对象中有包含attrs, slots, emit,expose。
- setup选项在组件创建之前执行,一旦props被解析,它就执行了。
2. 响应函数
2.1 ref
ref 的作用是创建一个响应式的数据对象,通过 .value 获取值,主要是对String、Number、Boolean等基本类型的数据响应。
首先来看一下,什么是无法响应的数据:
<template>
<div>
<h3>{{ num }}</h3>
<button @click="addNum">+++</button>
</div>
</template>
<script>
export default {
// setup中不能使用this
// 它必须要有一个返回值,它可以返回 json 对象、函数、async setup()
// json对象,对象中的属性,可以直接在模板中使用
// 函数,它就当作是一个组件结构,当前组件不需要 template => render
// async setup() 当作异步组件 结合 Suspense来使用
setup() {
// 定义了一个普通的变量,它不是一个响应式的变量
let num = 100
// methods
const addNum = () => {
num++
console.log('setup addNum', num)
}
// 此处相当于之前data配置
return { num, addNum }
}
}
</script>
<style lang="scss" scoped>
</style>
视图中的 num 并没有发生改变。
定义一个响应式的变量:
<template>
<div>
<!-- 如果num它是一个ref定义的响应式变量,在模板中调用,你无须写 num.value -->
<!-- 模板在解析的过程中会给你补上 -->
<h3>{{ num }}</h3>
<h1 ref="titleRef">我是一个标题</h1>
<button @click="addNum">+++</button>
</div>
</template>
<script>
import { ref, shallowRef } from 'vue'
export default {
// setup中不能使用this
// 它必须要有一个返回值,它可以返回 json 对象、函数、async setup()
// json对象,对象中的属性,可以直接在模板中使用
// 函数,它就当作是一个组件结构,当前组件不需要 template => render
// async setup() 当作异步组件 结合 Suspense来使用
setup() {
// 定义一个响应式的变量
// 如果ref赋值的为基础类型,是它是用Object.defineProperty来进行响应式处理
let num = ref(100)
// 绑定到dom元素或自定义组件上
let titleRef = ref(null)
// methods
const addNum = () => {
// num++
// 如果你的num它是一个ref定义的响应式变量,则在js中操作它,一定要xx.value来操作
num.value++
console.log('setup addNum', num.value)
console.log(titleRef.value)
}
// 此处相当于之前data配置
return { num, addNum, titleRef }
}
}
</script>
<style lang="scss" scoped>
</style>
对引用类型的响应式处理:
<template>
<div>
<div>{{ user1.age }}</div>
<input type="number" v-model.number="user1.age" />
<div>{{ user2.age.n }}</div>
<input type="number" v-model.number="user2.age.n" />
</div>
</template>
<script>
import { ref, shallowRef } from 'vue'
export default {
setup() {
// 如果ref赋值的为引用类型,它是用Proxy来进行响应式处理
// 引用类型
let user1 = ref({ id: 1, age: 10 })
let user2 = ref({ id: 1, age: { n: 100 } })
// 此处相当于之前data配置
return { user1, user2 }
}
}
</script>
<style lang="scss" scoped>
</style>
shallowRef:
只处理基本数据类型的响应式, 不进行对象的响应式处理。
<template>
<div>
<h3>{{ num.n }}</h3>
<button @click="addNum">+++</button>
</div>
</template>
<script>
import { shallowRef } from 'vue'
export default {
setup() {
// 只处理基本数据类型的响应式, 不进行对象的响应式处理。
let num = shallowRef({ n: 200 })
const addNum = () => {
// num.value.n++,不再会发生响应式
// 只能使用下面这种方式进行响应式处理,因为 shallowRef 只能处理基本数据类型
num.value = { n: num.value.n + 1 }
}
return { num, addNum }
}
}
</script>
<style lang="scss" scoped>
</style>
2.2 reactive
如果说 ref 的作用是把一个个数据拆分出来,那么 reactive 的作用就是把数据整合在一起。reactive 的响应式使用 proxy 实现的,它只能处理对象。
深层监听:
<template>
<div>
<h3>姓名:{{ state.name }} --- {{ state.age.n }}</h3>
<input type="number" v-model.number="state.age.n" />
<button @click="setName">修改姓名</button>
</div>
</template>
<script>
// ref 把数据拆分出来 id name age
// reactive 它可以把数据整合在一起,响应式用的就是Proxy,它只能处理对象
import { reactive } from 'vue'
export default {
setup() {
// 数据响应式的方式为 Proxy,它参数必须为object 深层监听
const state = reactive({
name: '张三',
age: { n: 22 }
})
const setName = () => {
// console.log(state)
// 修改reative中name属性值 ref-- xxx.value reactive -- state.xx = xx
state.name = Date.now() + ''
}
return { state, setName }
}
}
</script>
<style lang="scss" scoped>
</style>
浅层监听:
这种情况一般不用,用的更多的是 Ref ,使用 shallowReactive 大多是情况下只是为了性能优化。
<template>
<div>
<h3>姓名:{{ state.name }} --- {{ state.age.n }}</h3>
<input type="number" v-model.number="state.age.n" />
<button @click="setName">修改姓名</button>
</div>
</template>
<script>
// ref 把数据拆分出来 id name age
// reactive 它可以把数据整合在一起,响应式用的就是Proxy,它只能使用对象
import { shallowReactive } from 'vue'
export default {
setup() {
// 浅层监听,只监听对象的第1层
const state = shallowReactive({
// 姓名会发生变化
name: '张三',
// 年龄不会发生变化
age: { n: 22 }
})
const setName = () => {
state.name = Date.now() + ''
}
return { state, setName }
}
}
</script>
<style lang="scss" scoped>
</style>
2.3 toRef和toRefs
在使用 reactive 时,在模板中每次都要写 state. 会让代码变得很繁琐,于是我们可以想到,可不可以在 setup 的返回值中,将 state 结构出来,让模板中减少取数据的层级呢?
<template>
<div>
<h3>姓名:{{ name }} --- {{ age.n }}</h3>
<input type="number" v-model.number="age.n" />
<button @click="setName">修改姓名</button>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
const state = reactive({
name: '张三',
age: { n: 22 }
})
const setName = () => {
state.name = Date.now() + ''
console.log(state, { ...state })
}
// 在setup中reactive引用对象,不能解构,解构它就破坏了响应式
return { ...state, setName }
}
}
</script>
<style lang="scss" scoped>
</style>
很显然,我们的需求没有实现,在解构 state 后发现,修改姓名并不能响应式的发生改变,但是修改年龄依然可以实现响应式。
从控制台中我们可以发现,state 在解构前是被 proxy 劫持的,而解构后,就变味了普通的 js 对象,不再具有响应式,但是 age 仍然是一个 proxy 对象。
显然解构并不能实现在模板中减少数据提取的层级,那该怎么办呢?
vue3 为我们提供了 toRef 函数。
toRef:
<template>
<div>
<h3>姓名:{{ name }} --- {{ age.n }}</h3>
<input type="number" v-model.number="age.n" />
<button @click="setName">修改姓名</button>
</div>
</template>
<script>
import { reactive,toRef } from 'vue'
export default {
setup() {
const state = reactive({
name: '张三',
age: { n: 22 }
})
const setName = () => {
state.name = Date.now() + ''
console.log(toRef(state, 'name'))
}
return { age: toRef(state, 'age'), name: toRef(state, 'name'), setName }
}
}
</script>
<style lang="scss" scoped>
</style>
这时候我们可以看到,name 在使用 toRef 后,可以进行响应式,同时在控制台中也可以看到 name 还是一个 peoxy 对象。
toRef 在 setup 的return 中依然需要写很多代码,有没有更简便的写法呢?
vue3 为我们提供了 toRefs。
toRefs:
<template>
<div>
<h3>姓名:{{ name }} --- {{ age.n }}</h3>
<input type="number" v-model.number="age.n" />
<button @click="setName">修改姓名</button>
</div>
</template>
<script>
import { reactive,toRefs } from 'vue'
export default {
setup() {
const state = reactive({
name: '张三',
age: { n: 22 }
})
const setName = () => {
state.name = Date.now() + ''
}
return { ...toRefs(state), setName }
}
}
</script>
<style lang="scss" scoped>
</style>
toRef 和 toRefs 需要分场景使用,当我们只需要一两个数据时,用 toRef 比较合适,当需要用到所有数据时 toRefs 更加方便。
2.4 readonly
设置为只读属性。
<template>
<div>
<h3>{{ state.name }}</h3>
<button @click="setName">修改姓名</button>
</div>
</template>
<script>
// 赋值的数据它是一个只读的,不能修改,只能读取
import { readonly } from 'vue'
export default {
setup() {
let state = readonly({ name: '张三' })
const setName = () => {
state.name = Date.now() + ''
}
return { state, setName }
}
}
</script>
<style lang="scss" scoped>
</style>
2.5 customRef
用于自定义一个 ref。
首先通过 customRef 定义一个具备防抖功能的 ref ,自定义 ref 在工作中通常会封装在 hooks 中。
useDebouncedRef.js:
// 防抖
import { customRef } from 'vue'
const useDebouncedRef = (value, delay = 300) => {
let timeout
// track 响应收集,在获取时,收集
// trigger 响应 在你设置时触发,通知视图更新
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger()
}, delay)
}
}
})
}
export default useDebouncedRef
App组件:
<template>
<div>
<h3>{{ num }}</h3>
<input type="number" v-model="num" />
</div>
</template>
<script>
import useDebouncedRef from './hooks/useDebouncedRef'
export default {
setup() {
let num = useDebouncedRef(100)
return { num }
}
}
</script>
<style lang="scss" scoped></style>