前言
vue3的reactivity源码地址
- reactivity 响应式系统
- 实现的composition API有:
- reactive, shallowReactive, shallowReadonly, readonly
- ref, shallowRef
- toRef, toRefs
- effect:
- reactivity内部方法, 不暴露在外面
- 当数据变化时去执行effect函数
shared
vue3的shared源码地址
- shared: 多个包之间共享的内容
// 是不是个对象
export const isObject = (value) => typeof value == 'object' && value !== null
// 合并对象
export const extend = Object.assign
// 是不是数组
export const isArray = Array.isArray
// 是不是函数
export const isFunction = (value) => typeof value == 'function'
// 是不是数字
export const isNumber = (value) => typeof value == 'number'
// 是不是字符
export const isString = (value) => typeof value === 'string'
// 是不是正整数
export const isIntegerKey = (key) => parseInt(key) + '' === key
// 是不是自己的属性
let hasOwnpRroperty = Object.prototype.hasOwnProperty
export const hasOwn = (target, key) => hasOwnpRroperty.call(target, key)
// 是不是同一个值
export const hasChanged = (oldValue,value) => oldValue !== value
reactive
- reactive: 对数据进行Proxy代理(重点)
- shallowReactive: 对数据进行浅代理(就是只关注第一层)
- shallowReadonly: 对数据进行浅代理, 并且只能读取, 无法修改(不收集track)
- readonly: 只能读取, 无法修改(不收集track)
- 大致流程(以下面例子为准):
- 执行effect函数
state.arr.length
state.son.name
state.arr[3]
会走proxy的get
- 通过
track
收集当前的effect函数
- 当数据发生变化时会走proxy的
set
- 通过
trigger
会去再次执行当前的effect函数
- 只有在effect传递的函数中有的变量 才会去收集effect函数(相当于vue2中的watcher)
示例
<div id="app"></div>
<script src="../node_modules/@vue/reactivity/dist/reactivity.global.js"></script>
<script>
let { effect, reactive } = VueReactivity
let state = reactive({
name: 'tom',
age: 38,
son: {name: 'Bob', age: 18},
arr: [1,2,3,4,5]
})
effect(() => {
// app.innerHTML = state.name + state.name
app.innerHTML = `${state.arr.length}-${state.son.name}-${state.arr[3]}`
})
setTimeout(() => {
// state.arr.push(100)
state.son.name = 'Pretty'
state.arr.length = 1
}, 2000)
</script>
+---------------------+ +----------------------+
| | | |
| 5-Bob-4 +--->| 1-Pretty-undefined +
| | | |
+---------------------+ +----------------------+
reactive.ts
import { isObject } from "@vue/shared/src"
import { mutableHandlers, shallowReactiveHandlers, readonlyHandlers, shallowReadonlyHandlers } from './baseHandlers'
/**
* @description 拦截数据
*/
export function reactive(target) {
return createReactiveObject(target, false, mutableHandlers)
}
/**
* @description 拦截第一层数据(浅响应)
*/
export function shallowReactive(target) {
return createReactiveObject(target, false, shallowReactiveHandlers)
}
/**
* @description 只读数据
*/
export function readonly(target) {
return createReactiveObject(target, true, readonlyHandlers)
}
/**
* @description 浅的只读数据
*/
export function shallowReadonly(target){
return createReactiveObject(target, true, shallowReadonlyHandlers)
}
// 创建WeakMap, 响应还是只读
// 方便查找, 存储的key必须是对象
// 会自动垃圾回收, 不会造成内存泄漏
const reactiveMap = new WeakMap()
const readonlyMap = new WeakMap()
/**
* @description 创建数据代理
* @param target 要拦截的目标(数组或者对象)
* @param isReadonly 是不是只读
* @param baseHandlers Proxy第二个参数对象
*/
export function createReactiveObject(target, isReadonly, baseHandlers) {
if (!isObject) { return target }
const proxyMap = isReadonly ? readonlyMap : reactiveMap
const existProxy = proxyMap.get(target)
if (existProxy) { return existProxy }
const proxy = new Proxy(target, baseHandlers)
proxyMap.set(target, proxy)
return proxy
}
operators.ts
/**
* @description 收集effect时 枚举
*/
export const enum TrackOpTypes {
GET
}
/**
* @description 发布effect时 枚举
*/
export const enum TriggerOrTypes {
ADD,
SET
}
baseHandlers.ts
import { extend, hasChanged, hasOwn, isArray, isIntegerKey, isObject } from "@vue/shared/src"
import { track, trigger } from "./effect"
import { TrackOpTypes, TriggerOrTypes } from "./operators"
import { reactive, readonly } from "./reactive"
/**
* @description 创建get
* @param isReadonly 是不是只读
* @param shallow 是不是浅拦截
*/
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
// Reflect.get 具备返回值(target[key])
// 目标值类型不是Object 则抛出一个TypeError
const res = Reflect.get(target, key, receiver)
if (!isReadonly) {
// 收集effect
track(target, TrackOpTypes.GET, key)
}
if (shallow) { return res }
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
/**
* @description 创建set 只针对不是只读的数据
* @param shallow 是不是浅拦截
*/
function createSetter(shallow = false) {
return function set(target, key, value, receiver) {
const oldValue = target[key]
let hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key)
// Reflect.set 具备返回值(boolean)
const result = Reflect.set(target, key, value, receiver)
if (!hadKey) {
// 新增
trigger(target, TriggerOrTypes.ADD, key, value)
} else {
// 修改
trigger(target, TriggerOrTypes.SET, key, value, oldValue)
}
return result
}
}
const get = createGetter()
const shallowGet = createGetter(false, true)
const readonlyGet = createGetter(true)
const showllowReadonlyGet = createGetter(true, true)
const set = createSetter()
const shallowSet = createSetter(true)
export const mutableHandlers = {
get,
set
}
export const shallowReactiveHandlers = {
get: shallowGet,
set: shallowSet
}
// 只读 set时发出警告
let readonlyObj = {
set: (target, key) => {
console.warn(`set on key ${key} falied, 当前是只读属性`)
}
}
export const readonlyHandlers = extend({
get: readonlyGet,
}, readonlyObj)
export const shallowReadonlyHandlers = extend({
get: showllowReadonlyGet,
}, readonlyObj)
effect.ts
import { isArray, isIntegerKey } from "@vue/shared/src"
import { TriggerOrTypes } from "./operators"
/**
* @description uid effect唯一标识
* @description activeEffect 指向当前的effect
* @description effectStack 存储effect的栈, 结束一个去除一个
*/
let uid = 0
let activeEffect;
const effectStack = []
function createReactiveEffect(fn, options) {
const effect = function reactiveEffect() {
// 判断是为了防止 如: fn里 -> state.age++ 不停的更新 造成死循环
if (!effectStack.includes(effect)) {
try {
effectStack.push(effect)
activeEffect = effect
return fn()
} finally {
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
}
}
}
effect.id = uid++
effect._isEffect = true // 标识是响应式的effect
effect.raw = fn // 传入的原始函数
effect.options = options
return effect
}
/**
* @description 数据在effect传的函数中调用 当数据发生变化trigger会再次发布
* @param fn 传的函数
* @param options 选项 如是不是懒的(computed 默认不执行)
*/
export function effect(fn, options: any = {}) {
const effect = createReactiveEffect(fn, options)
if (!options.lazy) {
effect()
}
return effect
}
/**
* @description effect存储的变量 track收集的effect
* @description targetMap 结构
* {name: 'xxx', age: 000} => {
* name => [effect effect],
* age => [effect effect]
* },
* [1,2,3] => {
* 1 => [effect, efffect]
* }
*/
const targetMap = new WeakMap()
/**
* @description 让某个对象中的属性 收集当前对应的effect函数
* @description 只有在执行effect时, 并且在effect里的变量才会去收集
*/
export function track(target, type, key) {
// activeEffect 指向当前执行的effect
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)
}
}
/**
* @description 寻找属性对应的effect 让其执行(这里只有数组和对象)
* @param target 目标
* @param type 类型 新增 或者 修改
* @param key key
* @param newValue 新值
* @param oldValue 老值
*/
export function trigger(target, type, key?, newValue?, oldValue?) {
const depsMap = targetMap.get(target)
if (!depsMap) return
// 要发布的effect去重
// 将所有的要执行的effect 全部存到一个新的集合中 最终一起执行
const effects = new Set()
const add = (effectsToAdd) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => effects.add(effect))
}
}
// 数组 修改长度 traget.length = newValue
// 如 [1,2,3,4,5] => [1,2,3,4,5].length = 1
if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) => {
if (key === 'length' || key > newValue) {
add(dep)
}
})
} else {
// 可能是对象
// 这里肯定是修改 不能是新增
// 如果是新增 depsMap.get(key) -> undefined
if (key !== undefined) {
add(depsMap.get(key))
}
// 如果修改数组中的某个索引
// 如 arr=[1,2,3] -> arr[100]=1
switch (type) {
case TriggerOrTypes.ADD:
if (isArray(target) && isIntegerKey(key)) {
add(depsMap.get('length'))
}
break;
}
}
// 发布
effects.forEach((effect: any) => effect())
}
ref 和 toRef
ref
- ref大部分情况下 只针对单一变量
let name = ref('tom')
其实内部用的是Object.defineProperty(这里用的是class类属性访问器get和set)
- 如果
let state = ref({})
是对象, 将会去用reactive({})
API
- shallowRef 只做第一层的Object.defineProperty, 不会
reactive({})
示例
<script src="../node_modules/@vue/reactivity/dist/reactivity.global.js"></script>
<div id="app"></div>
<script>
const { ref, shallowRef, effect } = VueReactivity
let name = ref('Tom')
// let state = ref({a: 'a1', b: 'b1'})
effect(() => {
app.innerHTML = name.value
// app.innerHTML = state.value.a
})
setTimeout(() => {
name.value = 'Bob'
// state.value.a = 'a2'
}, 1000)
</script>
+---------------------+ +----------------------+
| | | |
| Tom +--->| Bob +
| | | |
+---------------------+ +----------------------+
toRef
- 调用proxy的变量, 做了一层代理
- toRefs只是循环调用toRef, 做了一层代理
示例
<script src="../node_modules/@vue/reactivity/dist/reactivity.global.js"></script>
<div id="app"></div>
<script>
const { effect, reactive, toRef, toRefs } = VueReactivity
let proxy = reactive({name:'Tom', age: 100})
// let r1 = toRef(proxy, 'name')
// let r2 = toRef(proxy, 'age')
const { name, age } = toRefs(proxy)
effect(()=>{
app.innerHTML = name.value + '-' + age.value
})
setTimeout(() => {
proxy.name = 'Bob'
}, 2000)
</script>
+---------------------+ +----------------------+
| | | |
| Tom-100 +--->| Bob-100 +
| | | |
+---------------------+ +----------------------+
ref.ts
import { hasChanged, isArray, isObject } from "@vue/shared/src"
import { track, trigger } from "./effect"
import { TrackOpTypes, TriggerOrTypes } from "./operators"
import { reactive } from "./reactive"
/**
* @description ref shallowRef
* @description reactive内部采用proxy ref中内部使用的是defineProperty
*/
export function ref(value) {
return createRef(value)
}
export function shallowRef(value) {
return createRef(value, true)
}
// 传进来的值 如果是对象 用reactive代理
const convert = (val) => isObject(val) ? reactive(val) : val
class RefImpl {
public _value;
public __v_isRef = true // 表示是一个ref属性
constructor(public rawValue, public shallow) {
this._value = shallow ? rawValue : convert(rawValue)
}
get value() {
// 收集effect
track(this, TrackOpTypes.GET, 'value')
return this._value
}
set value(newValue) {
if (hasChanged(newValue, this.rawValue)) {
this.rawValue = newValue
this._value = this.shallow ? newValue : convert(newValue)
// 发布 effect
trigger(this, TriggerOrTypes.SET, 'value', newValue)
}
}
}
function createRef(rawValue, shallow = false) {
return new RefImpl(rawValue, shallow)
}
/**
* @description toRef toRefs
* @description 将一个对象转换成ref类型 就是做了一层代理
*/
class ObjectRefImpl {
public __v_isRef = true
constructor(public target, public key) {}
// 如果原对象是响应式的就会track依赖收集
get value(){
return this.target[this.key]
}
// 如果原来对象是响应式的 就会trigger触发更新
set value(newValue){
this.target[this.key] = newValue
}
}
export function toRef(target, key) {
return new ObjectRefImpl(target, key)
}
export function toRefs(object) {
const ret = isArray(object) ? new Array(object.length) : {}
for (const key in object) {
ret[key] = toRef(object, key)
}
return ret
}
完
发表评论
还没有评论,快来抢沙发吧!