让Composition API 存在于 Vue3.0 项目以外
- 作者:陈大鱼头
- github:KRISACHAN
作为新特性 Composition API ,在 Vue3 正式发布之前一段时间就发布过了。
据文档介绍, Composition API 是一组低侵入式的、函数式的 API,使得我们能够更灵活地「组合」组件的逻辑。
不仅在 Vue 中,在其他的框架或原生 JS 也可以很好地被使用,下面我们就选取几个比较重要的 Composition API ,通过一些简单的例子来看看如何在其他项目中使用。
注:本文仅列出各个分类下比较重要的 API,想要查看全部可以点击下方链接进行查看:
https://github.com/vuejs/vue-next/tree/master/packages/reactivity
reactive API
createReactiveObject
createReactiveObject
函数是 reactive API 的核心,用于创建reactive
对象 。
在分享 API 之前,我们先看看其核心实现,由于篇幅有限,本文仅展示出理解后的简化版代码,代码如下:
function createReactiveObject(
target, // 要监听目标
isReadonly, // 是否只读
baseHandlers, // target 为 Object 或 Array 时的处理器,支持对数据的增删改查
collectionHandlers // target 为 Map/WeakMap 或 Set/WeakSet 时的处理器,支持对数据的增删改查
) {
if (target[ReactiveFlags.RAW] && !(isReadonly && target[ReactiveFlags.IS_REACTIVE]) {
// 当 target 已经是一个 Proxy 时,直接返回
// 例外情况:在 Proxy 里调用 readonly()
return target
}
// 当前对象已被监听过时,就直接返回被监听的对象
if (existingProxy) {
return existingProxy
}
// 如果是 Object Array Map/WeakMap Set/WeakSet 以外只能监听到值的数据,直接返回
if (targetType === TargetType.INVALID) {
return target
}
// 根据参数类型生成对应的 Proxy 对象,以及添加对应的处理器
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
reactive
接收一个普通对象然后返回该普通对象的响应式代理。等同于 2.x 的
Vue.observable()
示例如下:
import {
reactive,
isReactive // 判断是否是 reactive 对象
} from 'https://unpkg.com/@vue/reactivity/dist/reactivity.esm-browser.js'
const obj = {
nested: {
foo: 1
},
array: [{ bar: 2 }]
}
const value = 10
const observedObj = reactive(obj)
const observedValue = reactive(value)
console.log(isReactive(observedObj)) // true
console.log(isReactive(observedValue)) // true
shallowReactive
只为某个对象的私有(第一层)属性创建浅层的响应式代理,不会对“属性的属性”做深层次、递归的响应式代理,而只是保留原样。
示例如下:
const obj = {
nested: {
foo: 1
},
array: [{ bar: 2 }]
}
const value = 10
const unobservedObj = shallowReactive(obj)
const unobservedValue = shallowReactive(value)
console.log(isReactive(observedObj)) // false
console.log(isReactive(observedValue)) // false
effect API
createReactiveEffect
createReactiveEffect
是 effect API 的核心,用于创建监听用户自定义的reactive
对象的函数
在分享 API 之前,我们先看看其核心实现,由于篇幅有限,本文仅展示出理解后的简化版代码,代码如下:
function createReactiveEffect(
fn, // 用户创建的 reactive 对象变动执行回调
options = {
lazy, // 是否执行用户函数
scheduler, // 收集数据记录
onTrack, // 追踪用户数据的回调
onTrigger, // 追踪变更记录
onStop, // 停止执行
allowRecurse // 是否允许递归
}
) {
const effect = function reactiveEffect() {
if (!effectStack.includes(effect)) {
cleanup(effect) // 清空 effect
try {
enableTracking() // 往追踪用户数据的栈内添加当前 effect
effectStack.push(effect) // 往 effect 栈内添加 effect
activeEffect = effect // 将活动 effect 变成当前 effect
return fn() // 执行回调
} finally { // 删除当前记录
effectStack.pop()
resetTracking()
activeEffect = effectStack[effectStack.length - 1]
}
}
}
effect.id = uid++
effect._isEffect = true
effect.active = true
effect.raw = fn
effect.deps = []
effect.options = options
return effect
}
effect
effect
函数是 effect API 的核心。以WeakMap
为数据类型,是一个用于存储用户自定义函数的 订阅者。
示例如下:
let dummy
const counter = reactive({ num: 0 })
effect(() => (dummy = counter.num))
console.log(dummy === 0) // true
counter.num = 7
console.log(dummy === 7) // true
ref API
RefImpl
RefImpl
是 ref API 的核心,用于创建ref
对象
在分享 API 之前,我们先看看其核心实现,代码如下:
class RefImpl {
private _value // 存储当前 ref 对象的值
public __v_isRef = true // 确定是否为 ref 对象的变量 (只读)
constructor(
private _rawValue, // 用户传入的原始值
public readonly _shallow = false // 当前 ref 对象是否为 shallowRef
) {
// convert:如果传入的原始值为对象,则会被 convert 函数转换为 reactive 对象
this._value = _shallow ? _rawValue : convert(_rawValue)
}
get value() {
// 用于追踪用户输入的值变化
// track:effect api 的 track 函数,用于追踪用户行为,当前则是追踪用户的 get 操作
// toRaw:effect api 的 toRaw 函数,将 this 转化为用户输入值
track(toRaw(this), TrackOpTypes.GET, 'value')
return this._value
}
set value(newVal) {
if (hasChanged(toRaw(newVal), this._rawValue)) {
// 当前 ref 对象有变化时
// _rawValue / _value 变更
// trigger:effect api 的 trigger 函数,根据用户传入的值与操作类型来进行操作,当前则为将用户传入的值添加到值 map 里
this._rawValue = newVal
this._value = this._shallow ? newVal : convert(newVal)
trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
}
}
}
ref
接受一个参数值并返回一个响应式且可改变的 ref 对象。ref 对象拥有一个指向内部值的单一属性
.value
。如果传入 ref 的是一个对象,将调用reactive
方法进行深层响应转换。
示例如下:
const count = ref({
name: '鱼头',
type: '帅哥'
})
console.log(count.value.type) // 帅哥
count.value.type = '超级大帅哥'
console.log(count.value.type) // 超级大帅哥
shallowRef
创建一个 ref ,将会追踪它的
.value
更改操作,但是并不会对变更后的.value
做响应式代理转换(即变更不会调用reactive
)
示例如下:
const __shallowRef = shallowRef({ a: 1 })
let dummy
effect(() => {
dummy = __shallowRef.value.a
})
console.log(dummy) // 1
__shallowRef.value.a = 2
console.log(dummy) // 1
console.log(isReactive(__shallowRef.value)) // false
customRef
customRef
用于自定义一个ref
,可以显式地控制依赖追踪和触发响应,接受一个工厂函数,两个参数分别是用于追踪的track
与用于触发响应的trigger
,并返回一个带有get
和set
属性的对象。
示例如下:
let value = 1
let _trigger
const custom = customRef((track, trigger) => ({
get() {
track()
return value
},
set(newValue) {
value = newValue
_trigger = trigger
}
}))
let dummy
effect(() => {
dummy = custom.value
})
console.log(dummy) // 1
custom.value = 2
console.log(dummy) // 1
_trigger()
console.log(dummy) // 2
triggerRef
triggerRef
用于主动触发shallowRef
示例如下:
const __shallowRef = shallowRef({ a: 1 })
let dummy
effect(() => {
dummy = __shallowRef.value.a
})
console.log(dummy) // 1
__shallowRef.value.a = 2
console.log(dummy) // 1
console.log(isReactive(__shallowRef.value)) // false
triggerRef(__shallowRef)
console.log(dummy) // 2
computed API
ComputedRefImpl
ComputedRefImpl
是 ref API 的核心,用于创建computed
对象
在分享 API 之前,我们先看看其核心实现,由于篇幅有限,本文仅展示出理解后的简化版代码,代码如下:
class ComputedRefImpl {
private _value // 当前值
private _dirty = true // 当前值是否发生过变更
public effect // effect 对象
public __v_isRef = true; // 指定为 ref 对象
public [ReactiveFlags.IS_READONLY]: boolean // 是否只读
constructor(
getter, // getter
private _setter, // setter
isReadonly // 是否只读
) {
this.effect = effect(getter, {
lazy: true,
scheduler: () => {
if (!this._dirty) {
// 将变更状态变为 true
// trigger:effect api 的 trigger 函数,根据用户传入的值与操作类型来进行操作,当前则为将用户传入的值添加到值 map 里
this._dirty = true
trigger(toRaw(this), TriggerOpTypes.SET, 'value')
}
}
})
this[ReactiveFlags.IS_READONLY] = isReadonly
}
get value() {
if (this._dirty) {
// 返回当前值
// 将变更状态变为 false
this._value = this.effect()
this._dirty = false
}
// track:effect api 的 track 函数,用于追踪用户行为,当前则是追踪用户的 get 操作
track(toRaw(this), TrackOpTypes.GET, 'value')
return this._value
}
set value(newValue) {
this._setter(newValue)
}
}
computed
传入一个 getter 函数,返回一个默认不可手动修改的 ref 对象。或者传入一个拥有
get
和set
函数的对象,创建一个可手动修改的计算状态。
示例如下:
const count1 = ref(1)
const plus1 = computed(() => count1.value + 1)
console.log(plus1.value) // 2
plus1.value++ // Write operation failed: computed value is readonly
const count2 = ref(1)
const plus2 = computed({
get: () => count2.value + 1,
set: val => {
count2.value = val - 1
}
})
console.log(plus2.value) // 2
plus2.value = 0
console.log(plus2.value) // 0
❤️看完三件事
如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:
- 点赞,让更多的人也能看到介绍内容(收藏不点赞,都是耍流氓-_-)
- 关注公众号“前端劝退师”,不定期分享原创知识。
- 也看看其他文章
劝退师个人微信:huab119
也可以来我的GitHub
博客里拿所有文章的源文件:
前端劝退指南:https://github.com/roger-hiro/BlogFN一起玩耍呀