在Vue3中响应性可以在独立的包 (opens new window)中使用。

安装reactivity,在终端执行如下命令:

npm install @vue/reactivity

下面是reactivity包中导出的方法:

从源码中可以看到这个包里导出了很多方法,这里我们重点说下effect和reactive。

先来说下这两个方法的作用和基本使用方法。

在Vue3中要为 JavaScript 对象创建响应式状态,可以使用 reactive 方法。reactive接收一个对象作为参数并返回这个对象的响应式副本,它会将这个对象副本的所有 property 变为响应式的。

import { reactive } from '@vue/reactivity'

// 响应式状态
const state = reactive({
  count: 0
})

effect方法类似watchEffect方法,在响应式地跟踪其依赖项时立即运行一个函数,并在更改依赖项时重新运行它。

import { effect } from '@vue/reactivity'
const state = reactive({
  count: 0
})

effect(() => {
  console.log('执行了')
})

setTimeout(() => {
  state.count = 2
}, 2000)

我们还可以在html文件中引入包:

<script src="../node_modules/@vue/reactivity/dist/reactivity.global.js"></script>
<script>
  const { reactive } = VueReactivity;
  const state = reactive({
    count: 0
  })
</script>

这里我们使用第二种方法来探究Vue3中的响应式原理。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>探究计算属性</title>
</head>
<body>
  <div id="app"></div>
  <!-- <script src="../dist/bundle.js"></script> -->
  <script src="../node_modules/@vue/reactivity/dist/reactivity.global.js"></script>
  <script>
    const { reactive, effect }  = VueReactivity;
    // const state = { count: 1 };
    const state = reactive({count: 1});
    
    // 响应式和双向数据绑定不是一个  双向绑定指的是 v-model
    // 对数据进行劫持操作 vue2 使用Object.defineProperty vue3使用proxy
    // vue 当页面渲染时,会做依赖收集, vue2属性收集的是watcher,vue3收集的是effect
    // vue2 需要Object.defineProperty直接循环对象中的每一个属性,无法对不存在属性做处理
    // vue3 直接对对象做代理,对不存在属性也可以监控到
    
    effect(() => {
      console.log('object')
      app.innerHTML = state.count
    })

    setTimeout(() => {
      state.count = 2
    }, 1000);
  </script>
</body>
</html>

src/reactivity/reactive.ts

import { isObject } from "../shared/index"
import { mutableHandlers } from './baseHandlers'

export const enum ReactiveFlags {
  SKIP = '__v_skip',
  IS_REACTIVE = '__v_isReactive',
  IS_READONLY = '__v_isReadonly',
  RAW = '__v_raw'
}

export interface Target {
  [ReactiveFlags.SKIP]?: boolean
  [ReactiveFlags.IS_REACTIVE]?: boolean
  [ReactiveFlags.IS_READONLY]?: boolean
  [ReactiveFlags.RAW]?: any
}

export const reactive = (target: object) => {
  return createReactiveObject(target, mutableHandlers)
}

// 映射表中的 key 必须是对象,且不会有内存泄露的问题
export const reactiveMap = new WeakMap<Target, any>()

function createReactiveObject(target: Target, baseHandlers: ProxyHandler<any>) {
  // target 不是对象,直接返回
  if (!isObject(target)) return target

  const proxyMap = reactiveMap
  const existingProxy = proxyMap.get(target)
  // 如果 target 已经被代理过了,就不需要再次被代理了
  if (existingProxy) return existingProxy

  const proxy = new Proxy(target, baseHandlers)
  proxyMap.set(target, proxy)

  return proxy
}

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

src/reactivity/baseHandlers.ts

import { 
  hasOwn,
  makeMap,
  isArray,
  isObject, 
  isSymbol, 
  hasChanged,
  isIntegerKey 
} from '../shared/index'
import { Target, reactive } from './reactive'
import { TrackOpTypes, TriggerOpTypes } from './operations'
import { track, trigger } from './effect'

const get = createGetter()
const set = createSetter()
const isNonTrackableKeys = /*#__PURE__*/ makeMap(`__proto__,__v_isRef,__isVue`)

const builtInSymbols = new Set(
  Object.getOwnPropertyNames(Symbol)
    .map(key => (Symbol as any)[key])
    .filter(isSymbol)
)

export const mutableHandlers: ProxyHandler<object> = {
  get, // 当取值的时候将efect存储起来
  set, // 当设置值的时候通知对应的effect更新
}

function createGetter() {
  return function get(target: Target, key: string | symbol, receiver: object) {
    const res = Reflect.get(target, key, receiver)
    
    // 如果是内置的symbol,排除依赖收集
    if(
      isSymbol(key)
      ? builtInSymbols.has(key as symbol)
      : isNonTrackableKeys(key)
    ) {
      return res
    }

    track(target, TrackOpTypes.GET, key)

    return isObject(res) ? reactive(res) : res
  }
}

function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    // 设置的时候分为添加新的属性和修改原有属性

    const oldValue = (target as any)[key]

    // 对象 and 数组
    const hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key)

    const result = Reflect.set(target, key, value, receiver)
   
    if (!hadKey) {
      // 新增
      trigger(target, TriggerOpTypes.ADD, key, value)

      console.log('增加属性', value, oldValue)
    } else if (hasChanged(value, oldValue)) {
      console.log('修改属性')
      // 修改
      trigger(target, TriggerOpTypes.SET, key, value, oldValue)
    }

    return result
  }
}
/* 
默认加载页面是会调用一次effect,此时effect方法中的数据会进行取值操作,会执行get方法,让对应的属性保存当前的effect,如某个对象的某个属性对应的effect有几个

某个对象的某个属性变化了,需要找到对应的effect列表让它依次执行
*/

src/reactivity/effect.ts

import { 
  isMap,
  isArray, 
  EMPTY_OBJ, 
  isIntegerKey, 
} from '../shared/index'
import { TrackOpTypes, TriggerOpTypes } from './operations'

const __DEV__ =1
export const ITERATE_KEY = Symbol(__DEV__ ? 'iterate' : '')

type KeyToDepMap = Map<any, Dep>
const targetMap = new WeakMap<any, KeyToDepMap>()

// 为了保证当前effect和属性能对应上
/* 
如果我们用数组去存储就没法解决下面这种情况
effect1(() => {
  state.name
  effect2(() => {
    state.age
  })
  state.address
})

默认先调用effect1内部对state.name取值,把name属性和activeEffect(也就是effect1)关联起来

调用effect2内部对state.age取值,把age和activeEffect(也就是effect2)关联起来

effect2执行完毕 activeEffect 指向effect1

state.address再次取值此时关联到effect1
*/
const effectStack: ReactiveEffect[] = []
let activeEffect: ReactiveEffect | undefined

export const MAP_KEY_ITERATE_KEY = Symbol(__DEV__ ? 'Map key iterate' : '')

export interface DebuggerEventExtraInfo {
  newValue?: any
  oldValue?: any
  oldTarget?: Map<any, any> | Set<any>
}

export type DebuggerEvent = {
  effect: ReactiveEffect
  target: object
  type: TrackOpTypes | TriggerOpTypes
  key: any
} & DebuggerEventExtraInfo

export interface ReactiveEffectOptions {
  lazy?: boolean
  scheduler?: (job: ReactiveEffect) => void
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
  onStop?: () => void
  allowRecurse?: boolean
}

type Dep = Set<ReactiveEffect>

export interface ReactiveEffect<T = any> {
  (): T
  _isEffect: true
  id: number
  active: boolean
  raw: () => T
  deps: Array<Dep>
  options: ReactiveEffectOptions
  allowRecurse: boolean
}

// 需要将传递来的fn变成响应式的,数据变化了这个fn要重新执行
export function effect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions = EMPTY_OBJ
) {
  const effect = createReactiveEffect(fn, options)
  
  if (!options.lazy) {
    effect()
  }

  return effect
}

let uid = 0

function createReactiveEffect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions
): ReactiveEffect<T> {
  const effect = function reactiveEffect(): unknown {
    if (!effectStack.includes(effect)) {
      try {
        effectStack.push(effect)
        activeEffect = effect
        
        return fn() // 会执行取值逻辑,在取值逻辑中可以和effect做关联
      } finally {
        effectStack.pop()
        activeEffect = effectStack[effectStack.length - 1]
      }
    }

  } as ReactiveEffect

  effect.id = uid++
  effect.options = options

  return effect
}

// 某个对象中的某个属性依赖了哪些effect
/* 
 eg.

 effect(() => {
   state.name
 })

 effect(() => {
   state.name
 })

 state对象的name属性依赖了两个effect

 key是一个对象 值是一个属性  属性对应一个数组 这种形式源码中使用weakMap和set
*/
// 建立属性和effect之间的关联
export function track(target: object, type: TrackOpTypes, key: unknown) {
  if (activeEffect === undefined) return

  let depsMap = targetMap.get(target)

  if (!depsMap) targetMap.set(target, (depsMap = new Map()))

  let dep = depsMap.get(key)

  if (!dep) depsMap.set(key, (dep = new Set()))

  if (!dep.has(activeEffect)) {
    dep.add(activeEffect)
  }
  console.log(targetMap);
  
}

export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  const depsMap = targetMap.get(target)
  
  // 属性变化了但是没有依赖,直接跳过
  if (!depsMap) return

  let effects = new Set<ReactiveEffect>()

  const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
    console.log('effectsToAdd', effectsToAdd,key);
    
    if (effectsToAdd) {
      effectsToAdd.forEach(effect => {
        if (effect !== activeEffect || effect.allowRecurse) {
          effects.add(effect)
        }
      })
    }
  }

  /* 
  Map{"toString" => Set(1)},{"join" => Set(1)},{"length" => Set(1)},{"0" => Set(1)},{"1" => Set(1)},{"2" => Set(1)}
  */
  if (key === 'length' && isArray(target)) {
    depsMap.forEach((dep, key) => {
      if (key === 'length' || key >= (newValue as number)) add(dep)
    })
  }else {
    // 处理target是对象的情况
    console.log('depsMap', depsMap.get(key));
    /* 
    Map(1) {"count" => Set(1)}
     */
    if (key !== void 0) add(depsMap.get(key))
    
    switch (type) {
      case TriggerOpTypes.ADD:
        if (!isArray(target)) {
          add(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            add(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        } else if (isIntegerKey(key)) {
          // new index added to array -> length changes
          add(depsMap.get('length'))
        }
        break
      case TriggerOpTypes.SET:
        if (isMap(target)) {
          add(depsMap.get(ITERATE_KEY))
        }
        break
    }
  }

  const run = (effect: ReactiveEffect) => {
    // 计算属性effect
    if (effect.options.scheduler) {
      effect.options.scheduler(effect)
    } else {
      // 渲染effect
      effect()
    }
  }

  effects.forEach(run)
}

src/shared/index.ts

export const isObject = (val: unknown): val is Record<any, any> =>
  val !== null && typeof val === 'object'

const __DEV__ = 1

export const EMPTY_OBJ: { readonly [key: string]: any } = __DEV__
? Object.freeze({})
: {}

export const isString = (val: unknown): val is string => typeof val === 'string'

export const isIntegerKey = (key: unknown) =>
  isString(key) &&
  key !== 'NaN' &&
  key[0] !== '-' &&
  '' + parseInt(key, 10) === key

export const isArray = Array.isArray

const hasOwnProperty = Object.prototype.hasOwnProperty
export const hasOwn = (
  val: object,
  key: string | symbol
): key is keyof typeof val => hasOwnProperty.call(val, key)

export const hasChanged = (value: any, oldValue: any): boolean => value !== oldValue && (value === value || oldValue === oldValue)

export const isSymbol = (val: unknown): val is symbol => typeof val === 'symbol'

export function makeMap(
  str: string,
  expectsLowerCase?: boolean
): (key: string) => boolean {
  const map: Record<string, boolean> = Object.create(null)
  const list: Array<string> = str.split(',')
  for (let i = 0; i < list.length; i++) {
    map[list[i]] = true
  }
  return expectsLowerCase ? val => !!map[val.toLowerCase()] : val => !!map[val]
}

export const objectToString = Object.prototype.toString
export const toTypeString = (value: unknown): string =>
  objectToString.call(value)

export const isMap = (val: unknown): val is Map<any, any> =>
  toTypeString(val) === '[object Map]'

export const isFunction = (val: unknown): val is Function =>
  typeof val === 'function'

export const NOOP = () => {}

weakMap和Map的区别

假设我们用的是Map

let obj = {}
let map = new Map()
map.set(obj, 'f')

obj = null // 这样操作obj是不销毁的,因为被map引用了当时用了weakMap就会销毁

前面我们简单说了下Vue3中的响应式原理,effect这个函数一般我们不会直接使用,effect一般都会内置到渲染组件、计算属性里面

reactive不会默认递归,而是取对应属性的时候再对内层对象进行代理。默认会先去执行一次effect,在取数据时将这个effect收集起来