10

让Vue3 Composition API 存在于你 Vue 以外的项目中

 4 years ago
source link: https://segmentfault.com/a/1190000038471330
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

作为新特性 Composition API ,在 Vue3 正式发布之前一段时间就发布过了。

距文档介绍, Composition API 是一组低侵入式的、函数式的 API,使得我们能够更灵活地「组合」组件的逻辑。

不仅在 Vue 中,在其他的框架或原生 JS 也可以很好地被使用,下面我们就选取几个比较重要的 Composition API ,通过一些简单的例子来看看如何在其他项目中使用。

注:本文仅列出各个分类下比较重要的 API,想要查看全部可以点击下方链接进行查看:

https://github.com/vuejs/vue-...

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 对象拥有一个指向内部值的单一属性 .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,并返回一个带有 getset 属性的对象。

示例如下:

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 对象。或者传入一个拥有 getset 函数的对象,创建一个可手动修改的计算状态。

示例如下:

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

Recommend

  • 22

    The next version of Vue is around the corner and we can already try some new features, like Vue Composition API, which is heavily inspired by React Hooks. A lot of developers are excited about it, others are not so sure. L...

  • 11

    Vue Composition API 响应式包装对象原理上一篇文章Vue 3.0 最新进展,Composition API中,笔者通过描述Vue Composition API 的最新修...

  • 21
    • zhuanlan.zhihu.com 4 years ago
    • Cache

    Vue 3.0 最新进展,Composition API

    Vue 3.0 最新进展,Composition API在上一篇文章Vue 3.0 前瞻,体验 Vue Function API,笔者通过尝试

  • 8
    • blog.poetries.top 4 years ago
    • Cache

    Vue3之Composition API详解

    Composition API 也叫组合式API,是Vue3.x的新特性。 通过创建 Vue 组件,我们可以将接口的可重复部分及其功能提取到可重用的代码段中。仅此一项就可以使我们的应用程序在可维护性和灵活性方面走得更远。然而,我们的经验...

  • 8

    Vue Composition API vs React Hooks - the core difference Arek Nawo | 05 May 2021 | 4 min read

  • 10

    BackDiscovering Vue Composition API with examplesMay 25th, 2021 · 6 min read

  • 11
    • shenzilong.cn 3 years ago
    • Cache

    了解 vue composition-api 原理

    了解 vue composition-api 原理 写在前面的心得 1. 不要在 setup 内直接使用 setInterval 与 setTimeout 这类需要手动清理副作用的 api 1. 因为人很容易忘记清理他的副作用,应该自行封装一个类似于计算属性的

  • 5
    • lianpf.github.io 2 years ago
    • Cache

    对比 React Hooks 和 Vue Composition API

    目录 场景 hook 的时代意义 React Hooks Vue Composition API 差别 总结 场景 先理解…目录hook 的时代...

  • 8
    • www.syncfusion.com 2 years ago
    • Cache

    Vue Composition API vs. React Hooks

    Vue Composition API vs. React HooksReact Hooks were introduced with the React 16.8 update, and since then, they...

  • 5

    Creating Computed Properties with Vue's Composition API Oct 10, 2022 To create a computed property with the Vue's Composition API, you should call Vue's computed() function. For example, the followin...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK