在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收集起来