TNTWeb - 全称腾讯新闻前端团队,组内小伙伴在Web前端、NodeJS开发、UI设计、挪动APP等大前端畛域都有所实际和积攒。

目前团队次要反对腾讯新闻各业务的前端开发,业务开发之余也积攒积淀了一些前端基础设施,赋能业务提效和产品翻新。

团队提倡开源共建,领有各种技术大牛,团队Github地址:https://github.com/tnfe

本文作者dravenwu

本篇文章将会围绕Vue3的另外一个次要的文件夹reactivity来进行解说,也就是Vue3中对外裸露的compositionApi的局部,,越来越有React Hooks的滋味了。reactivity文件夹上面蕴含多个文件,次要性能在于computed、effect、reactive、ref;其余的文件是为其进行服务的,另外还有一个主入口文件index。reactivity上面对外裸露的所有api可见下图,咱们本篇文件会联合应用对这些性能进行源码剖析。

注释

注释在这里,正式开始。

computed

computed的含意与Vue2中的含意是一样的,计算属性;应用形式也是和Vue2中差不多的,有两种应用形式:

computed应用

const {reactive, readonly, computed, ref} = Vue;const app = Vue.createApp({});app.component('TestComponent', {    setup(props) {        // reactive        const state = reactive({            count: 0,            number: 10        })        // computed getter        const computedCount = computed(() => {            return state.count + 10        })        // computed set get        const computedNumber = computed({            get: () => {                return state.number + 100            },            set: (value) => {                state.number = value - 50            }        })        const changeCount = function(){            state.count++;            computedNumber.value = 200        }        return {            state,            changeCount,            computedCount,            computedNumber        }    },    template: `        <div>            <h2>init count:<i>{{state.count}}</i></h2>            <h2>computedCount:<i>{{computedCount}}</i></h2>            <h2>computedNumber:<i>{{computedNumber}}</i></h2>            <button @click="changeCount">changeCount</button>        </div>    `})app.mount('#demo')

下面代码能够看到两次对computed的应用,第一次传递的是一个函数,第二次传递的是一个蕴含get和set的对象。

computed源码剖析

接下来,咱们来看下computed的源码:

// @file packages/reactivity/src/computed.tsexport function computed<T>(  getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>) {  let getter: ComputedGetter<T>  let setter: ComputedSetter<T>  if (isFunction(getterOrOptions)) {    getter = getterOrOptions    setter = __DEV__      ? () => {          console.warn('Write operation failed: computed value is readonly')        }      : NOOP  } else {    getter = getterOrOptions.get    setter = getterOrOptions.set  }  return new ComputedRefImpl(    getter,    setter,    isFunction(getterOrOptions) || !getterOrOptions.set  ) as any}

下面是computed的入口的源码,此处和Vue2中的写法是一样的,都是对参数进行判断,生成getter和setter,这里最初调用的是ComputedRefImpl;

// packages/reactivity/src/computed.tsclass ComputedRefImpl<T> {  private _value!: T  private _dirty = true  public readonly effect: ReactiveEffect<T>  public readonly __v_isRef = true;  public readonly [ReactiveFlags.IS_READONLY]: boolean  constructor(    getter: ComputedGetter<T>,    private readonly _setter: ComputedSetter<T>,    isReadonly: boolean  ) {    this.effect = effect(getter, {      lazy: true,      scheduler: () => {        if (!this._dirty) {          this._dirty = true          trigger(toRaw(this), TriggerOpTypes.SET, 'value')        }      }    })    this[ReactiveFlags.IS_READONLY] = isReadonly  }  get value() {    if (this._dirty) {      this._value = this.effect()      this._dirty = false    }    track(toRaw(this), TrackOpTypes.GET, 'value')    return this._value  }  set value(newValue: T) {    this._setter(newValue)  }}

如上,是ComputedRefImpl的源码。ComputedRefImpl是一个class,外部蕴含_value、_dirty、effect、__v_isRef、ReactiveFlags.IS_READONLY等属性,还包含constructor和get、set等函数。理解的同学都晓得,会首先调用构造函数也就是constructor;调用effect为effect属性赋值,把isReadonly赋值给ReactiveFlags.IS_READONLY属性,对于effect,咱们前面讲这块。此时ComputedRefImpl执行实现。

当获取以后computed的值的时候,如下面应用中computedCount在template中进行获取值的时候,会调用下面class内的get办法,get办法外部调用的是this.effect进行数据的获取,_dirty属性是为了数据的缓存,依赖未发生变化,则不会调用effect,应用之前的value进行返回。track是跟踪以后get调用的轨迹。

当为computed赋值的时候,如下面应用中computedNumber.value = 200的时候,,会调用下面class内的set办法,set外部还是调用了之前传递进来的函数。

reactive

接下来对reactive的解说

reactive 应用

reactive官网给的解释是:返回对象的响应式正本。先来看下reactive的应用

const {reactive} = Vue;const app = Vue.createApp({});app.component('TestComponent', {    setup(props) {        // reactive        const state = reactive({            count: 0        })        const changeCount = function(){            state.count++;        }        return {            state,            changeCount        }    },    template: `        <div>            <h2>reactive count:<i>{{state.count}}</i></h2>            <button @click="changeCount">changeCount</button>        </div>    `})app.mount('#demo')

当点击changeCount的时候,state.count会++,同时映射到h2-dom。

reactive 源码解读

// @file packages/reactivity/src/reactive.tsexport function reactive(target: object) {  // if trying to observe a readonly proxy, return the readonly version.  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {    return target  }  return createReactiveObject(    target,    false,    mutableHandlers,    mutableCollectionHandlers  )}

如上源码能够看到,如果target有值并且target的[ReactiveFlags.IS_READONLY]属性,也就是__v_isReadonly为true的话,会间接返回以后对象,不做任何解决,前面对state.count的扭转也不会映射到dom当中。如果不满足下面条件,则会调用createReactiveObject函数,传递4个参数:

  • target为原始对象;
  • 第二个是isReadonly,为false;
  • 第三个参数mutableHandlers是reactive对应的处理函数;
  • 第四个参数是对于汇合类型的对象进行解决的函数。

对于这个外围的函数,咱们待会来进行解说。

readonly 应用

当初咱们来看下Vue3提供给咱们的reactivity上面的第二个api:readonly。

官网给出的定义是:获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。只读代理是深层的:拜访的任何嵌套 property 也是只读的

const {readonly} = Vue;const app = Vue.createApp({});app.component('TestComponent', {    setup(props) {        const read = readonly({count: 1})        const changeRead = function(){            read.count++;        }        return {            read,            changeRead        }    },    template: `        <div>            <h2>readonly count:<i>{{read.count}}</i></h2>            <button @click="changeRead">changeRead</button>        </div>    `})app.mount('#demo')

下面代码,是readonly的应用,在此试验了一下对readonly返回后的后果read,进行了扭转的尝试,发现是扭转不了的,属于只读,同时还会打印正告Set operation on key "count" failed: target is readonly.

readonly 源码解读

// @file packages/reactivity/src/reactive.tsexport function readonly<T extends object>(  target: T): DeepReadonly<UnwrapNestedRefs<T>> {  return createReactiveObject(    target,    true,    readonlyHandlers,    readonlyCollectionHandlers  )}

下面就是readonly的源码入口,与reactive一样,都是调用的createReactiveObject函数:

  • 第一个参数还是target;
  • 第二个是isReadonly,为true;
  • 第三个参数readonlyHandlers是readonly对应的处理函数;
  • 第四个参数是对于汇合类型的对象进行解决的readonly所对应的函数。

shallowReactive 应用

官网文档给的解释:创立一个响应式代理,该代理跟踪其本身 property 的响应性,但不执行嵌套对象的深度响应式转换 (裸露原始值)。来看下shallowReactive的应用

const {shallowReactive} = Vue;const app = Vue.createApp({});app.component('TestComponent', {    setup(props) {        const state = shallowReactive({            foo: 1,            nested: {                bar: 2            }        })        const change = function(){            state.foo++            state.nested.bar++        }        return {            state,            change        }    },    template: `        <div>            <h2>foo:<i>{{state.foo}}</i></h2>            <h2>bar:<i>{{state.nested.bar}}</i></h2>            <button @click="change">change</button>        </div>    `})app.mount('#demo')

下面代码根本是齐全依照官网来写的,不过,试了下成果和官网上的成果不一样,并不是shallow类型,而是对外部的属性也进行了监听,bar的扭转也会响应式的反映到dom当中去。也不晓得是我姿态不对,还是Vue3的bug。

shallowReactive 源码解读

// @file packages/reactivity/src/reactive.tsexport function shallowReactive<T extends object>(target: T): T {  return createReactiveObject(    target,    false,    shallowReactiveHandlers,    shallowCollectionHandlers  )}

下面就是shallowReactive的源码入口,与reactive和readonly一样,都是调用的createReactiveObject函数:

  • 第一个参数还是target;
  • 第二个是isReadonly,为false;
  • 第三个参数shallowReactiveHandlers是shallowReactive对应的处理函数;
  • 第四个参数是对于汇合类型的对象进行解决的shallowReactive所对应的函数。

    shallowReadonly 应用

    官网给出的解释:创立一个代理,使其本身的 property 为只读,但不执行嵌套对象的深度只读转换 (裸露原始值)。来看下应用:

    const {shallowReadonly} = Vue;const app = Vue.createApp({});app.component('TestComponent', {  setup(props) {      const state = shallowReadonly({          foo: 1,          nested: {              bar: 2          }      })      const change = function(){          state.foo++          state.nested.bar++      }      return {          state,          change      }  },  template: `      <div>          <h2>foo:<i>{{state.foo}}</i></h2>          <h2>bar:<i>{{state.nested.bar}}</i></h2>          <button @click="change">change</button>      </div>  `})app.mount('#demo')

    下面代码根本是齐全依照官网来写的,foo的扭转不被容许,依照官网阐明state.nested.bar是容许被扭转的,在下面例子中,发现state.nested.bar的值是会扭转的,然而不会响应到dom上。

    shallowReadonly 源码解读

    // @file packages/reactivity/src/reactive.tsexport function shallowReadonly<T extends object>(target: T): Readonly<{ [K in keyof T]: UnwrapNestedRefs<T[K]> }> {return createReactiveObject(  target,  true,  shallowReadonlyHandlers,  readonlyCollectionHandlers)}

    下面就是shallowReadonly的源码入口,与reactive和readonly一样,都是调用的createReactiveObject函数:

  • 第一个参数还是target;
  • 第二个是isReadonly,为true;
  • 第三个参数shallowReadonlyHandlers是shallowReadonly对应的处理函数;
  • 第四个参数是对于汇合类型的对象进行解决的shallowReadonly所对应的函数。

isReadonly

isReadonly:查看对象是否是由readonly创立的只读代理。

应用如下:

const only = readonly({    count: 1})isOnly = isReadonly(only) // true

源码如下:

export function isReadonly(value: unknown): boolean {  return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])}

ReactiveFlags.IS_READONLY是一个字符串,值为:__v_isReadonly,挂到对象下面就是属性,判断以后对象的__v_isReadonly属性是否是true,并返回。

isReactive

isReadonly:查看对象是否是 reactive创立的响应式 proxy。

应用如下:

const tive = reactive({    count: 1})isOnly = isReactive(tive) // true

源码如下:

export function isReactive(value: unknown): boolean {  if (isReadonly(value)) {    return isReactive((value as Target)[ReactiveFlags.RAW])  }  return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])}

首先调用了下面提到的isReadonly办法判断是否是readonly创立的对象;如果是的话,则进一步应用以后对象的RAW属性调用isReactive来判断;如果不是则判断__v_isReactive是否为true;返回判断的后果。

ReactiveFlags.RAW是一个字符串,值为:__v_raw,挂到对象下面就是属性,也就是原始对象,判断是否是reactive代理的原始对象;
ReactiveFlags.IS_READONLY也是一个字符串,值为:__v_isReactive,挂到对象下面就是属性

isProxy

isProxy:查看对象是否是reactive 或 readonly创立的代理。

应用如下:

const tive = reactive({    count: 1})const only = readonly({    count: 1})is1 = isProxy(tive) // trueis2 = isProxy(only) // true

源码如下:

export function isProxy(value: unknown): boolean {  return isReactive(value) || isReadonly(value)}

调用下面提到的isReadonly办法和isReactive判断是否是proxy的对象。

markRaw

markRaw:标记一个对象,使其永远不会转换为代理。返回对象自身。

应用如下:

const foo = markRaw({})console.log(isReactive(reactive(foo))) // falseconst bar = reactive({ foo })console.log(isReactive(bar)) // trueconsole.log(isReactive(bar.foo)) // false

从下面应用中能够看到,markRaw只对以后对象自身无效,被标记的对象作为属性的时候,大对象bar还是能够进行响应式解决的,然而bar外面的以后被标记的对象foo,还是一个非响应式对象,永远是foo对象自身。

export function markRaw<T extends object>(value: T): T {  def(value, ReactiveFlags.SKIP, true)  return value}export const def = (obj: object, key: string | symbol, value: any) => {  Object.defineProperty(obj, key, {    configurable: true,    enumerable: false,    value  })}

下面能够看到markRaw的源码,就是给要标记的对象减少了一个属性(ReactiveFlags.SKIP, 也就是__v_skip),并赋值true,所有要给以后对象进行响应式解决的时候,都会被疏忽。

toRaw

toRaw:返回 reactive 或 readonly 代理的原始对象。这是一个本义口,可用于长期读取而不会引起代理拜访/跟踪开销,也可用于写入而不会触发更改。不倡议保留对原始对象的长久援用。请审慎应用。

既然Vue让咱们审慎应用,咱们还是在能够不应用的的时候不应用的好,这个就是把代理的原始对象进行返回。

const obj = {    project: 'reactive'}const reactiveObj = reactive(obj)console.log(toRaw(reactiveObj) === obj) // true

源码:

export function toRaw<T>(observed: T): T {    return (        (observed && toRaw((observed as Target)[ReactiveFlags.RAW])) || observed    )}

返回以后对象的ReactiveFlags.RAW(也就是__v_raw)属性指向的对象,也就是对象自身,对于在什么中央给ReactiveFlags.RAW赋值的,前面会看到这部分。

createReactiveObject

下面的reactive、readonly、shallowReactive、shallowReadonly,都用到了createReactiveObject函数,当初咱们来看看这个函数的源码。

function createReactiveObject(target: Target, isReadonly: boolean, baseHandlers: ProxyHandler<any>, collectionHandlers: ProxyHandler<any>) {    if (!isObject(target)) {        if (__DEV__) {            console.warn(`value cannot be made reactive: ${String(target)}`)        }        return target    }    if (target[ReactiveFlags.RAW] && !(isReadonly && target[ReactiveFlags.IS_REACTIVE])) {        return target    }    const proxyMap = isReadonly ? readonlyMap : reactiveMap    const existingProxy = proxyMap.get(target)    if (existingProxy) {        return existingProxy    }    const targetType = getTargetType(target)    if (targetType === TargetType.INVALID) {        return target    }    const proxy = new Proxy(        target,        targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers    )    proxyMap.set(target, proxy)    return proxy}

源码解读:

  • 首先,target得是一个对象,不是对象的话,间接返回以后值;当然Vue3也提供了对值的响应式的办法:ref,前面讲。
  • 判断有原始对象且,不是只读或者不是响应式的对象,则返回以后对象,这个中央真TM绕。
  • 依据是否是isReadonly,获取到代理存储的map,,如果之前代理过,曾经存在,则把之前代理过的proxy返回。
  • 判断target的类型,getTargetType外部会对target对象进行判断,返回是common、collection或者invalid;如果不可用类型(invalid),则间接返回以后对象。此处会用到下面讲到的__v_skip。可用的类型就两个,一个是common,一个是collection;
  • 接下来就是没有代理过,获取代理的过程。new Proxy,如果是collection则应用传递进来的collectionHandlers,否则(也就是common)则应用baseHandlers;
  • 代理存储所应用的map,存储以后proxy;
  • 返回以后proxy。

通过下面reactive、readonly、shallowReactive、shallowReadonly的解说,能够看到对于汇合和common类型,提供了几种不同的解决对象,对象中所蕴含的内容也是不一样的,咱们在这里来比照着看下:

basehandler:


如上图,能够看到,basehandler外面所提供的函数,咱们一一来看下。

deleteProperty

// @file packages/reactivity/src/baseHandlers.tsfunction deleteProperty(target: object, key: string | symbol): boolean {  const hadKey = hasOwn(target, key)  const oldValue = (target as any)[key]  const result = Reflect.deleteProperty(target, key)  if (result && hadKey) {    trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)  }  return result}
  • 获取以后对象是否有以后key => hadKey;
  • 获取到以后的value存储为oldValue;
  • 调用Reflect.deleteProperty进行对以后对象target删除以后key的操作,返回后果为是否删除胜利->result;
  • 删除胜利,并且有以后key,则调用trigger,触发effect。
  • 返回删除是否胜利的后果。

    ownKeys

    // @file packages/reactivity/src/baseHandlers.tsfunction ownKeys(target: object): (string | number | symbol)[] {track(target, TrackOpTypes.ITERATE, ITERATE_KEY)return Reflect.ownKeys(target)}

    这个函数很简略了就,获取target对象本人的属性key;跟踪获取的轨迹,而后调用Reflect.ownKeys获取后果。

    has

    // @file packages/reactivity/src/baseHandlers.tsfunction has(target: object, key: string | symbol): boolean {const result = Reflect.has(target, key)if (!isSymbol(key) || !builtInSymbols.has(key)) {  track(target, TrackOpTypes.HAS, key)}return result}
  • 调用Reflect.has获取以后对象是否有以后key;
  • 不是Symbol类型的key,或者不是Symbol自身的属性,调用track跟踪has调用的轨迹。
  • 返回后果,result。

    createSetter

    function createSetter(shallow = false) {return function set(  target: object,  key: string | symbol,  value: unknown,  receiver: object): boolean {  const oldValue = (target as any)[key]  if (!shallow) {    value = toRaw(value)    if (!isArray(target) && isRef(oldValue) && !isRef(value)) {      oldValue.value = value      return true    }  } else {}  const hadKey = isArray(target) && isIntegerKey(key)      ? Number(key) < target.length      : hasOwn(target, key)  const result = Reflect.set(target, key, value, receiver)  if (target === toRaw(receiver)) {    if (!hadKey) {      trigger(target, TriggerOpTypes.ADD, key, value)    } else if (hasChanged(value, oldValue)) {      trigger(target, TriggerOpTypes.SET, key, value, oldValue)    }  }  return result}}

    函数工厂,依据shallow生成set函数。set函数承受4个参数:target为指标对象;key为设置的属性;value为设置的值;receiver为Reflect的额定参数(如果遇到 setter,receiver则为setter调用时的this值)。

  • 首先获取到oldValue;
  • 如果非浅响应式,也就是正式状况的时候,获取到value的原始对象并赋值给value,如果target对象不是数组且oldValue是ref类型的响应式类型,并且新value不是ref类型的响应式,为oldValue赋值(ref类型的响应式对象,须要为对象的value赋值)。
  • 上面也就是深度响应式的代码逻辑了。
  • 如果是数组并且key是数字类型的,则直接判断下标,否则调用hasOwn获取,是否蕴含以后key => hadKey;
  • 调用Reflect.set进行设置值;
  • 如果指标对象和receiver的原始对象相等,则hadKey,调用trigger触发add操作;否则,调用trigger触发set操作。
  • 把set解决的后果返回,result。

    createGetter

    function createGetter(isReadonly = false, shallow = false) {return function get(target: Target, key: string | symbol, receiver: object) {  if (key === ReactiveFlags.IS_REACTIVE) {    return !isReadonly  } else if (key === ReactiveFlags.IS_READONLY) {    return isReadonly  } else if (    key === ReactiveFlags.RAW &&    receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)  ) {    return target  }  const targetIsArray = isArray(target)  if (targetIsArray && hasOwn(arrayInstrumentations, key)) {    return Reflect.get(arrayInstrumentations, key, receiver)  }  const res = Reflect.get(target, key, receiver)  const keyIsSymbol = isSymbol(key)  if (    keyIsSymbol      ? builtInSymbols.has(key as symbol)      : key === `__proto__` || key === `__v_isRef`  ) {    return res  }  if (!isReadonly) {    track(target, TrackOpTypes.GET, key)  }  if (shallow) {    return res  }  if (isRef(res)) {    const shouldUnwrap = !targetIsArray || !isIntegerKey(key)    return shouldUnwrap ? res.value : res  }  if (isObject(res)) {    return isReadonly ? readonly(res) : reactive(res)  }  return res}}

    函数工厂,依据shallow生成get函数。get函数承受3个参数:target为指标对象;key为设置的属性;receiver为Reflect的额定参数(如果遇到 setter,receiver则为setter调用时的this值)。

  • 如果key是__v_isReactive,则间接返回!isReadonly,通过下面的图可得悉,reactive相干的调用createGetter,传递的是false,也就是会间接返回true;
  • 如果key是__v_isReadonly,则间接返回isReadonly,同样的通过下面的图能够得悉,readonly相干的调用createGetter,传递的是true,也就是会间接返回true;
  • 如果key是__v_raw并且receiver等于proxyMap存储的target对象的proxy,也就是获取原始对象,则间接返回target;
  • 如果是数组的话,则会走自定义的办法,arrayInstrumentations;arrayInstrumentations是和Vue2中对数组的改写是一样的逻辑;
  • 上面会对key进行判断,如果Symbol对象并且是Set外面自定义的办法;或者key为__proto__或__v_isRef,则间接把Reflect.get(target, key, receiver)获取到的值间接返回;
  • 如果非只读状况下,调用track跟踪get轨迹;
  • 如果是shallow,非深度响应式,也是间接把下面获取到的res间接返回;
  • 如果是ref对象,则会调用.value获取值进行返回;
  • 剩下的状况下,如果失去的res是个对象,则依据isReadonly调用readonly或reactive获取值,进行返回;
  • 最初有一个res保底返回;

    collectionHandler:


    来看下createInstrumentationGetter的源码,下面图中三个都是调用此办法生成对应的解决对象。

    function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {const instrumentations = shallow  ? shallowInstrumentations  : isReadonly    ? readonlyInstrumentations    : mutableInstrumentationsreturn (  target: CollectionTypes,  key: string | symbol,  receiver: CollectionTypes) => {  if (key === ReactiveFlags.IS_REACTIVE) {    return !isReadonly  } else if (key === ReactiveFlags.IS_READONLY) {    return isReadonly  } else if (key === ReactiveFlags.RAW) {    return target  }  return Reflect.get(    hasOwn(instrumentations, key) && key in target      ? instrumentations      : target,    key,    receiver  )}}

    下面createInstrumentationGetter函数依据isReadonly和shallow返回一个函数;

  • 依据isReadonly和shallow,获取到对应的instrumentations;此对象蕴含了对汇合操作的所有办法;
  • 而后就把上面的函数进行了返回,createInstrumentationGetter相当于是一个闭包;
  • 返回的函数外面在执行调用的时候,会先对key进行判断,如果拜访的是Vue的公有变量,也就是下面的__v_isReactive、__v_isReadonly、__v_raw等,会间接给出不同的返回;
  • 如果不是Vue的下面的三个公有变量,则会调用Reflect.get来获取对象的值;instrumentations,也就是重写的办法汇合,不在此汇合外面的,则会间接调用target本人的办法。

reactive完结

至此,reactive文件外面的这些办法咱们都梳理了一遍,简略的做了源码的剖析和解读,感兴趣的读者能够深刻源码钻研下Vue中为何这样实现。

Refs

接下来咱们将开始对ref及其从属办法的应用和解说。

ref

首先,咱们对ref进行解说,官网给出的解释是:承受一个外部值并返回一个响应式且可变的 ref 对象。ref 对象具备指向外部值的单个 property .value。

先来看下ref的应用。

const {ref} = Vue;const app = Vue.createApp({});app.component('TestComponent', {    setup(props) {        const count = ref(0)        const obj = ref({number: 10})        const change = function(){            count.value++;            obj.value.number++        }        return {            count,            obj,            change        }    },    template: `        <div>            <h2>count:<i>{{count}}</i></h2>            <h2>number:<i>{{obj.number}}</i></h2>            <button @click="change">change</button>        </div>    `})app.mount('#demo')

下面是ref的应用,能够看到ref承受的是一个一般类型的值或者是一个对象,Vue官网给出的例子是不蕴含传递对象的,其实这也就是Vue不提倡应用ref来响应式一个对象,如果是对对象的响应式,Vue还是提倡应用下面reactive来实现;第二个须要留神点在于template中对ref对象的援用是不须要加上value属性来获取值,如上ref对象count在js中须要count.value,然而在template种只需count即可

来看下ref的源码实现

// @file packages/reactivity/src/ref.tsexport function ref<T extends object>(  value: T): T extends Ref ? T : Ref<UnwrapRef<T>>export function ref<T>(value: T): Ref<UnwrapRef<T>>export function ref<T = any>(): Ref<T | undefined>export function ref(value?: unknown) {  return createRef(value)}function createRef(rawValue: unknown, shallow = false) {  if (isRef(rawValue)) {    return rawValue  }  return new RefImpl(rawValue, shallow)}class RefImpl<T> {  private _value: T  public readonly __v_isRef = true  constructor(private _rawValue: T, private readonly _shallow = false) {    this._value = _shallow ? _rawValue : convert(_rawValue)  }  get value() {    track(toRaw(this), TrackOpTypes.GET, 'value')    return this._value  }  set value(newVal) {    if (hasChanged(toRaw(newVal), this._rawValue)) {      this._rawValue = newVal      this._value = this._shallow ? newVal : convert(newVal)      trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)    }  }}const convert = <T extends unknown>(val: T): T =>  isObject(val) ? reactive(val) : val  export const hasChanged = (value: any, oldValue: any): boolean =>  value !== oldValue && (value === value || oldValue === oldValue)

下面是依照运行轨迹来看的Vue3中ref的源码局部;依据ref的申明能够看到ref承受任何参数,返回类型为Ref对象,外部调用的是createRef;

  • createRef函数外部会先对value进行判断,如果曾经是ref对象的话,间接返回以后value,否则就调用new RefImpl来生成ref对象进行返回。
  • constructor外面会判断是否是浅响应_shallow,浅的话,间接返回_rawValue,否则调用convert来返回;能够看到除了公有属性_value外,还有一个__v_isRef的只读属性为true;
  • convert外面则会对val进行判断了,对象则调用reactive,否则间接返回val,此处也就能够看到下面ref也能够承受对象作为参数的原因了。
  • get外面会跟踪调用轨迹,track;返回以后value;
  • set外面会调用hasChanged判断是否产生了扭转,此处会对NaN进行check,因为NaN与啥都不相等;设置新的值,同时调用trigger触发set调用。

    isRef

    isRef很显著就是判断是否是ref对象的办法。应用如下:

    const count = ref(0)const is = isRef(count)const is2 = isRef(10)

    来看下源码,源码也很简略:

    export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>export function isRef(r: any): r is Ref {return Boolean(r && r.__v_isRef === true)}

    此处就应用到了RefImpl外面那个只读属性了,判断__v_isRef是否为true就能够了。

    shallowRef

    官网给出的解释:创立一个 ref,它跟踪本人的 .value 更改,但不会使其值成为响应式的。
    shallowRef的源码如下:

    export function shallowRef<T extends object>(value: T): T extends Ref ? T : Ref<T>export function shallowRef<T>(value: T): Ref<T>export function shallowRef<T = any>(): Ref<T | undefined>export function shallowRef(value?: unknown) {return createRef(value, true)}

    shallowRef与ref的调用流程是一样的,不过是多了个参数,导致_shallow为true,就在RefImpl外面调用时,间接返回了以后value,而不会进行到convert函数。

    unRef

    官网解释:如果参数为 ref,则返回外部值,否则返回参数自身。 源码如下:

    export function unref<T>(ref: T): T extends Ref<infer V> ? V : T {return isRef(ref) ? (ref.value as any) : ref}

    的确如官网所说,就一行代码,ref对象则返回其value,否则间接返回ref。

    triggerRef

    官网给出的解释:手动执行与 shallowRef 关联的任何成果。 ,比拟含糊,艰深点就是手动触发一次effect的调用;
    看下应用:

    const count = ref(0)const change = function(){  count.value++;  triggerRef(count)}const shallow = shallowRef({  greet: 'Hello, world'})watchEffect(() => {  console.log(count.value)  console.log(shallow.value.greet)})shallow.value.greet = 'Hello, universe'

    源码如下:

    export function triggerRef(ref: Ref) {trigger(ref, TriggerOpTypes.SET, 'value', __DEV__ ? ref.value : void 0)}

toRef

官网给出的解释是:能够用来为源响应式对象上的 property 属性创立一个 ref。而后能够将 ref 传递进来,从而放弃对其源 property 的响应式连贯。 简略形容就是为对象的一个属性减少一个援用,这个援用能够随便应用,响应式不变。来看下源码:

export function toRef<T extends object, K extends keyof T>(  object: T,  key: K): Ref<T[K]> {  return isRef(object[key])    ? object[key]    : (new ObjectRefImpl(object, key) as any)}class ObjectRefImpl<T extends object, K extends keyof T> {  public readonly __v_isRef = true  constructor(private readonly _object: T, private readonly _key: K) {}  get value() {    return this._object[this._key]  }  set value(newVal) {    this._object[this._key] = newVal  }}

这部分的代码比较简单,也比拟容易读懂,和下面RefImpl一样的是都减少了一个只读的__v_isRef属性。

toRefs

官网对toRefs给出的解释是:将响应式对象转换为一般对象,其中后果对象的每个 property 都是指向原始对象相应 property 的ref。 艰深点形容就是把响应式对象的每个属性,都变成ref对象。来看下源码:

export function toRefs<T extends object>(object: T): ToRefs<T> {  if (__DEV__ && !isProxy(object)) {    console.warn(`toRefs() expects a reactive object but received a plain one.`)  }  const ret: any = isArray(object) ? new Array(object.length) : {}  for (const key in object) {    ret[key] = toRef(object, key)  }  return ret}

这里尤为要求是一个响应式的对象,非响应式对象还会打印正告。for循环调用下面讲到的toRef函数,把对象外面的每个属性都变为ref对象。

customRef

官网给出的解释是:创立一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式管制。它须要一个工厂函数 来看下customRef的源码:

class CustomRefImpl<T> {  private readonly _get: ReturnType<CustomRefFactory<T>>['get']  private readonly _set: ReturnType<CustomRefFactory<T>>['set']  public readonly __v_isRef = true  constructor(factory: CustomRefFactory<T>) {    const { get, set } = factory(      () => track(this, TrackOpTypes.GET, 'value'),      () => trigger(this, TriggerOpTypes.SET, 'value')    )    this._get = get    this._set = set  }  get value() {    return this._get()  }  set value(newVal) {    this._set(newVal)  }}export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {  return new CustomRefImpl(factory) as any}

绝对应的,应用的时候,承受的是一个factory,factory是一个函数,参数为track和trigger,同时factory的返回须蕴含两个函数,一个为get,一个为set。track就是effect的track,trigger也是effect的trigger;来看下应用:

const {customRef} = Vue;const app = Vue.createApp({});function useDebouncedRef(value, delay = 200) {    let timeout    return customRef((track, trigger) => {        return {            get() {                track()                return value            },            set(newValue) {                clearTimeout(timeout)                timeout = setTimeout(() => {                    value = newValue                    trigger()                }, delay)            }        }    })}app.component('TestComponent', {    setup(props) {        return {            text: useDebouncedRef('hello')        }    },    template: `        <div>            <input v-model="text" />        </div>    `})app.mount('#demo')

下面是customRef的应用的例子,和官网的例子是一样的,可能实现防抖,同时也可能显式的管制什么时候调用track来跟踪和什么时候来调用trigger来触发扭转。

Refs完结

下面咱们对refs外面的几种办法做了源码的解读和局部的api是如何应用的,对于Vue3为何提供了两种响应式的计划:reactive和Refs,这其实就和代码格调有关系了,有的同学习惯应用对象,而有的同学习惯应用变量,Vue3为这两种计划都提供了,想用哪个用哪个。

effect

其实能够看到下面好多中央都用到了这个办法,包含effect、track、trigger等都是effect外面提供的办法,effect外面提供的办法属于Vue的外部办法,不对外裸露。上面咱们挨个来看看这部分的源码,

isEffect

isEffect是为判断是否是有副作用的函数。来看下源码:

export function isEffect(fn: any): fn is ReactiveEffect {  return fn && fn._isEffect === true}

能够看到下面的判断,就是对函数的_isEffect进行判断,非常简单。

effect

effect作为Vue2和Vue3中外围的局部,都有这个的概念,重中之重,来看下这部分的源码:

export function effect<T = any>(  fn: () => T,  options: ReactiveEffectOptions = EMPTY_OBJ): ReactiveEffect<T> {  if (isEffect(fn)) {    fn = fn.raw  }  const effect = createReactiveEffect(fn, options)  if (!options.lazy) {    effect()  }  return effect}function createReactiveEffect<T = any>(  fn: () => T,  options: ReactiveEffectOptions): ReactiveEffect<T> {  const effect = function reactiveEffect(): unknown {    if (!effect.active) {      return options.scheduler ? undefined : fn()    }    if (!effectStack.includes(effect)) {      cleanup(effect)      try {        enableTracking()        effectStack.push(effect)        activeEffect = effect        return fn()      } finally {        effectStack.pop()        resetTracking()        activeEffect = effectStack[effectStack.length - 1]      }    }  } as ReactiveEffect  effect.id = uid++  effect._isEffect = true  effect.active = true  effect.raw = fn  effect.deps = []  effect.options = options  return effect}let shouldTrack = trueconst trackStack: boolean[] = []export function pauseTracking() {  trackStack.push(shouldTrack)  shouldTrack = false}export function enableTracking() {  trackStack.push(shouldTrack)  shouldTrack = true}export function resetTracking() {  const last = trackStack.pop()  shouldTrack = last === undefined ? true : last}

如上,就是effect局部的源码。顺着执行程序一步步走下来。

  • 调用方调用effect函数,参数为函数fn,options(默认为{});
  • 判断是否曾经是effect过的函数,如果是的话,则间接把原函数返回。
  • 调用createReactiveEffect生成以后fn对应的effect函数,把下面的参数fn和options间接传进去;
  • 判断options外面的lazy是否是false,如果不是懒解决,就间接调用下对应的effect函数;
  • 返回生成的effect函数。

接下来看下createReactiveEffect函数的调用过程。

  • 为effect函数赋值,临时先不思考reactiveEffect函数外部到底干了什么,只有明确创立了个函数,并赋值给了effect变量。
  • 而后为effect函数增加属性:id, _isEffect, active, raw, deps, options
  • 把effect返回了。

上面咱们回到下面非lazy状况下,调用effect,此时就会执行reactiveEffect函数。

  • 首先判断了是否是active状态,如果不是,阐明以后effect函数曾经处于生效状态,间接返回return options.scheduler ? undefined : fn()
  • 查看调用栈effectStack外面是否有以后effect,如果无以后effect,接着执行上面的代码。
  • 先调用cleanup,把以后所有依赖此effect的全副清掉,deps是个数组,元素为Set,Set外面放的则是ReactiveEffect,也就是effect;
  • 把以后effect入栈,并将以后effect置为以后沉闷effect->activeEffect;后执行fn函数;
  • finally,把effect出栈,执行实现了,把activeEffect还原到之前的状态;
  • 其中波及到调用轨迹栈的记录。和shouldTrack是否须要跟踪轨迹的解决。

stop

stop办法是用来进行以后effect的。属于Vue3外部办法,来看下源码:

export function stop(effect: ReactiveEffect) {  if (effect.active) {    cleanup(effect)    if (effect.options.onStop) {      effect.options.onStop()    }    effect.active = false  }}
  • 调用cleanup清空掉,和下面调用cleanup一样。
  • 执行以后effect.options.onnStop钩子函数。
  • 把以后effect的active状态置为false。

结言

本篇文章次要围绕reactivity文件夹外面提供给大家应用的compositionApi的局部进行了绝对应的应用和源码解读,大家感兴趣的还是去读下这部分的源码,毕竟这是Vue3新出的性能,越来越react的一步......

欢送大家一起来探讨Vue3,刚出的版本,带来了新的同时,必定也会带着意想不到的惊喜(bug),让咱们发现它,解决掉它,也是一种提高,也是避免本人踩坑的好办法。