vue组合式API相关知识

setup

setup 函数是一个新的组件选项。作为在组件内使用 Composition API 的入口点

  • 它在创建组件实例,初始化props后调用,从生命周期的角度看,他会在beforeCreate钩子之前被调用
  • setup会返回一个对象,该对象属性会被合并到组件模板渲染的上下文中。(ref定义的数据会自动解开,无需.value引用)
  • setup(props, context){},接收props作为一个参数,且是响应式的,如果对其解构则会失去响应。第二个参数是上下文对象,类似vue2中的this,可以对其进行解构获取内部的一些属性。
  • this在setup中不可用

响应式系统API

reactive

接收一个普通对象然后返回该普通对象的响应式代理。返回的对象不等于原始的对象。

1
const obj = reactive({ count: 0 })

等同于 2.x 的 Vue.observable()

ref

接受一个参数值并返回一个响应式且可改变的 ref 对象。ref 对象拥有一个指向内部值的单一属性 .value

如果传入的是一个对象,则会调用reactive进行处理。

  • 当ref作为渲染上下文的属性(通过setup函数返回),在模板中使用会自动解套,不需要写.value
  • 当ref作为reactive对象中的属性被访问的时候也会自动解套,从 Array 或者 Map 等原生集合类中访问 ref 时,则不会。
  • 可以通过传递范型参数来覆盖默认的推导
1
const strOrNum = ref<string | number>('123')

computed

计算属性可有两种方式

  • 传入一个getter函数,返回一个不可修改的ref对象

    1
    2
    const count = ref<number>(0)
    const comp = computed(() => count.value + 1)
  • 传入一个拥有get和set函数的对象,可手动修改

    1
    2
    3
    4
    5
    6
    7
    const count = ref<number>(0)
    const comp = computed({
    get: () => count.value + 1,
    set: val => {
    count.value = val - 1
    }
    })

readonly

传入一个对象(响应式、普通)或ref,返回一个只读的代理,只读代理是深层的,对象内部的任何嵌套属性都是只读的。

watchEffect

立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数。

停止监听

watchEffect 在组件的 setup() 函数或生命周期钩子被调用时, 侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。

也可以调用其返回值来停止监听:

1
2
3
4
5
6
const stop = watchEffect(() => {
/* ... */
})

// 之后
stop()

清理失效时的回调

副作用传入的函数可以接收一个 onInvalidate 函数作入参, 用来注册清理失效时的回调

失效回调会被触发的条件:

  • 副作用即将重新执行时
  • 侦听器被停止

副作用刷新时机

当一个用户定义的副作用函数进入队列时, 会在所有的组件更新后执行:

可以传递一个拥有 flush 属性的对象作为选项,默认是‘post’,在组件更新后执行副作用,值也可以是同步‘sync’、之前‘pre’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 同步运行
watchEffect(
() => {
/* ... */
},
{
flush: 'sync',
}
)

// 组件更新前执行
watchEffect(
() => {
/* ... */
},
{
flush: 'pre',
}
)

watch

它类似于vue2.x的this.$watch,需要侦听特定的数据源,并在回调函数中执行副作用。默认情况是懒执行的,也就是说仅在侦听的源变更时才执行回调。

与watchEffect对比,watch特点:

  • 懒执行副作用
  • 更明确哪些状态的改变会触发侦听器重新运行副作用
  • 访问侦听状态变化前后的值

监听单个数据源

监听器的数据源可以是一个getter函数也可以是ref

1
2
3
4
5
6
7
const state = reactive({ name: 'YoLin' })
watch(() => state.name, (curVal, prevVal) => {...})

const name = ref('YoLin')
watch(name, (curVal, prevVal) => {
...
})

监听多个数据源

可以使用数组来同时侦听多个源

1
watch([ref1, ref2], ([curRef1, curRef2], [prevRef1, prevRef2]) => {})

生命周期钩子函数

只能在setup() 期间同步使用

与 2.x 版本生命周期相对应的组合式 API

  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • update -> onUpdated
  • beforeDestroy -> onBeforeUnmount
  • destroy -> onUnmounted

新增的钩子函数 (用于调试)

  • onRenderTracked

  • onRenderTriggered

依赖注入

provide/inject提供依赖注入,只能在setup中调用

1
2
provide(key, 'dark')
inject(key, 'light')

inject 接受一个可选的的默认值作为第二个参数。如果未提供默认值,并且在 provide 上下文中未找到该属性,则 inject 返回 undefined

模板Refs

可以获得对模板内元素或组件实例的引用,我们可以像往常一样在 setup() 中声明一个 ref 并返回

将root暴露在渲染上下文,通过ref=”root”,绑定到dom节点上作为其ref

在 Virtual DOM patch 算法中,如果一个 VNode 的 ref 对应一个渲染上下文中的 ref,则该 VNode 对应的元素或组件实例将被分配给该 ref。 这是在 Virtual DOM 的 mount / patch 过程中执行的,因此模板 ref 仅在渲染初始化后才能访问。

  • 配合 render 函数 / JSX 的用法
1
2
3
4
5
6
7
8
9
10
11
12
13
export default {
setup() {
const root = ref(null)

return () =>
h('div', {
ref: root,
})

// 使用 JSX
return () => <div ref={root} />
},
}
  • v-for 中使用

    使用函数型的 ref(3.0 提供的新功能)来自定义处理方式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <template>
    <div v-for="(item, i) in list" :ref="el => { divs[i] = el }">
    {{ item }}
    </div>
    </template>

    <script>
    import { ref, reactive, onBeforeUpdate } from 'vue'

    export default {
    setup() {
    const list = reactive([1, 2, 3])
    const divs = ref([])

    // 确保在每次变更之前重置引用
    onBeforeUpdate(() => {
    divs.value = []
    })

    return {
    list,
    divs,
    }
    },
    }
    </script>

响应式系统工具集

unref

如果参数是一个 ref 则返回它的 value,否则返回参数本身。

1
2
let str = ref('666')
let strVal = unref(str) // '666'

toRef

toRef 可以用来为一个 reactive 对象的属性创建一个 ref。这个 ref 可以被传递并且能够保持响应性。

1
2
3
4
5
6
7
8
9
let state = reactive({
foo: 1,
bar: 2
})
let fooRef = toRef(state, 'foo')
fooRef.value++
console.log(state.foo) // 2
state.foo++
console.log(fooRef.value) // 3

当要将一个 prop 中的属性作为 ref 传给组合逻辑函数时,toRef 就派上了用场:

1
2
3
4
5
export default {
setup(props) {
useSomeFeature(toRef(props, 'foo'))
},
}

toRefs

把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref ,和响应式对象 property 一一对应。

当想要从一个组合逻辑函数中返回响应式对象时,用 toRefs 是很有效的,该 API 让消费组件可以 解构 / 扩展(使用 ... 操作符)返回的对象,并不会丢失响应性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// hook.js
export const useFeature = () => {
const state = reactive({
a: 1,
b: 2
})
return toRefs(state)
}

// component or page
import { useFeature } from './hook.js'
export default {
setup() {
// 可以解构,不会丢失响应性
const { a, b } = useFeature()
return {
a,
b,
}
},
}

isRef

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

isProxy

检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

isReactive

检查一个对象是否是由 reactive 创建的响应式代理。

isReadonly

检查一个对象是否是由 readonly 创建的只读代理。

高级响应式系统 API

customRef

customRef 用于自定义一个 ref,可以显式地控制依赖追踪和触发响应,接受一个工厂函数,两个参数分别是用于追踪的 track 与用于触发响应的 trigger,并返回一个一个带有 getset 属性的对象

  • 使用customRef实现带防抖功能的v-model

    1
    <input v-model="text" />
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    function useDebouncedRef(value, delay = 200) {
    let timer
    return customRef((track, trigger) => {
    return {
    get() {
    track()
    return value
    },
    set(newVal) {
    clearTimeout(timer)
    timer = setTimeout(() => {
    value = newVal
    trigger()
    }, delay)
    }
    }
    })
    }

    export default {
    setup() {
    return {
    text: useDebouncedRef('hello'),
    }
    },
    }

markRaw

显式标记一个对象为“永远不会转为响应式代理”,函数返回这个对象本身。

1
2
const foo = markRaw({})
console.log(isReactive(reactive(foo))) // false

shallowReactive

只为某个对象的私有(第一层)属性创建浅层的响应式代理,不会对“属性的属性”做深层次、递归地响应式代理,而只是保留原样。

shallowReadonly

只为某个对象的自有(第一层)属性创建浅层的只读响应式代理,同样也不会做深层次、递归地代理,深层次的属性并不是只读的。

shallowRef

创建一个 ref ,将会追踪它的 .value 更改操作,但是并不会对变更后的 .value 做响应式代理转换(即变更不会调用 reactive

1
2
3
4
5
const foo = shallowRef({})
// 更改对操作会触发响应
foo.value = {}
// 但上面新赋的这个对象并不会变为响应式对象
isReactive(foo.value) // false

toRaw

返回由 reactivereadonly 方法转换成响应式代理的普通对象。这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发更改。不建议一直持有原始对象的引用。请谨慎使用。

1
2
3
4
const foo = {}
const reactiveFoo = reactive(foo)

console.log(toRaw(reactiveFoo) === foo) // true