响应式原理
TIP
vue3.0采用Monorepo管理项目,代码采用ts编写,rollup打包
一、响应式API实现
0.使用方法
const { reactive, shallowReactive, readonly, shallowReadonly } = VueReactivity;
let obj = { name: 'test', age: { n: 10 } };
const state1 = reactive(obj); // 普通代理 》 递归代理全部属性
const state2 = shallowReactive(obj); // 浅代理 》 只代理第一层
const state3 = readonly(obj); // 只读代理 》 所有的属性不能修改
const state4 = shallowReadonly(obj); // 浅只读代理 》 第一层不能修改
1.入口文件
// packages/reactivity/src/index.ts
// 只导出方法,不实现功能
export {
reactive,
shallowReactive,
shallowReadonly,
readonly
} from './reactive';
2.四个函数
// packages/reactivity/src/reactive.ts
import {
mutableHandlers,
shallowReactiveHandlers,
readonlyHandlers,
shallowReadonlyHandlers
} from './baseHandlers';
import { isObject } from "@vue/shared";
// 定义两个缓存对象,用于存储被代理过的对象
// weakmap的优点:会自动垃圾回收,不会造成内存泄漏, 存储的key只能是对象
const reactiveMap = new WeakMap(); // 缓存普通代理
const readonlyMap = new WeakMap(); // 缓存只读代理
/**
* 科里化-抽象出每个方法的公共Proxy进行拦截
* @param target 目标对象
* @param isReadonly 是否只读
* @param baseHandlers 处理器的对象方法,包含get和set
*/
export function createReactiveObject(target, isReadonly, baseHandlers) {
// 1.判断目标是否为对象,不是对象不用拦截,直接返回,只拦截对象
if (!isObject(target)) {
return target;
}
// 2.判断对象是否已经被代理过了,如果被代理过了就直接返回
const proxyMap = isReadonly ? readonlyMap : reactiveMap;
const existProxy = proxyMap.get(target);
if (existProxy) {
return existProxy;
}
// 3.没有被代理过的对象直接代理了,放入缓存并返回代理对象
const proxy = new Proxy(target, baseHandlers);
proxyMap.set(target, proxy);
// 4. 返回代理对象
return proxy;
}
// 普通代理
export function reactive(target) {
return createReactiveObject(target, false, mutableHandlers);
}
// 浅普通代理:只代理第一层
export function shallowReactive(target) {
return createReactiveObject(target, false, shallowReactiveHandlers);
}
// 浅的只读代理
export function shallowReadonly(target) {
return createReactiveObject(target, true, shallowReadonlyHandlers);
}
// 只读代理
export function readonly(target) {
return createReactiveObject(target, true, readonlyHandlers);
}
3.处理器对象
import { isObject, extend, isArray, isIntegerKey, hasOwn, hasChanged } from "@vue/shared/src";
import { readonly, reactive } from "./reactive";
import { TrackOpTypes, TriggerOrTypes } from "./operators";
import { track, trigger } from "./effect";
/**
* 科里化-定义get方法的拦截处理
* @param isReadonly 是否只读
* @param shallow 是否为浅代理
*/
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
// 取值
const res = Reflect.get(target, key, receiver);
// 如果不是只读需要进行依赖搜集,等数据变化后更新对应的视图
if (!isReadonly) {
console.log('依赖收集');
}
// 如果为浅代理,则直接返回
if (shallow) {
return res;
}
// 如果为对象,需要递归进行代理(取值才代理 》 懒代理)
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res);
}
return res;
}
}
// 定义四个get方法
const get = createGetter();
const shallowGet = createGetter(false, true);
const readonlyGet = createGetter(true);
const showllowReadonlyGet = createGetter(true, true);
/**
* 科里化-定义set方法的拦截处理
* @param shallow 是否为浅代理
*/
function createSetter(shallow = false) {
return function set(target, key, value, receiver) {
// 更新值
const result = Reflect.set(target, key, value, receiver);
return result;
}
}
// 定义set方法
const set = createSetter();
const shallowSet = createSetter(true);
let readonlyObjSet = {
set: (target, key) => {
console.warn(`set on key ${key} falied: target is readonly.`)
}
}
// 导出四个函数
export const mutableHandlers = {
get,
set
}
export const shallowReactiveHandlers = {
get: shallowGet,
set: shallowSet
}
export const readonlyHandlers = extend({
get: readonlyGet,
}, readonlyObjSet)
export const shallowReadonlyHandlers = extend({
get: showllowReadonlyGet,
}, readonlyObjSet)
二、effect实现
0.使用方法
<div id="app"></div>
<script src="../node_modules/@vue/reactivity/dist/reactivity.global.js"></script>
<script>
let {effect, reactive} = VueReactivity;
let state = reactive({name: 'test', age: 10, arr: [1, 2, 3]});
// 定义需要依赖收集的属性
effect(() => {
console.log('render');
// app.innerHTML = state.name + state.age;
app.innerHTML = state.arr[2] + state.arr.length;
});
// 赋值》更新操作
setTimeout(() => {
// state.name = 'xxx'; // 更改属性
// state.aaa = 'xxx'; // 新增属性
// state.arr.push(1); // 给数组新增值
// state.arr[2] = 100; // 更新原有数组索引和值
// state.arr[3] = 100; // 给数组新增索引和值
state.arr.length = 100; // 更改数组长度
}, 1000);
</script>
1.入口文件
// packages/reactivity/src/index.ts
export {
effect
} from './effect'
2.主要结构
// packages/reactivity/src/effect.ts
/**
* effect函数
* @param fn 需要执行的函数
* @param options 配置对象
*/
export function effect(fn, options: any = {}) {
// 创建响应式的effect,可以做到数据变化重新执行
const effect = createReactiveEffect(fn, options);
// 如果不是懒配置,则默认需要先执行一次effect
if (!options.lazy) {
effect();
}
return effect;
}
let uid = 0; // effect的标识量
let activeEffect; // 存储当前的effect
const effectStack = []; // effect的记录栈
/**
* 创建响应式的effect
* @param fn 需要执行的函数
* @param options 配置对象
*/
function createReactiveEffect(fn, options) {
const effect = function reactiveEffect() {
// 为了避免添加重复的effect和规避死循环,需要加一次判断
if (!effectStack.includes(effect)) {
try {
effectStack.push(effect);
activeEffect = effect;
// 执行fn函数
return fn();
} finally {
// fn函数执行完了之后,需要出栈,并且修改当前的activeEffect
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
}
}
}
effect.id = uid++; // 制作一个effect的唯一标识,用于区分effect
effect._isEffect = true; // 用于标识当前effect是响应式的
effect.raw = fn; // 保留effect对应的原函数
effect.options = options; // 保留用户配置的属性对象
return effect;
}
3.依赖收集
- 在get取值的时候进行依赖收集
// packages/reactivity/src/baseHandlers.ts
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
// 取值
const res = Reflect.get(target, key, receiver);
// 如果不是只读需要进行依赖搜集,等数据变化后更新对应的视图
if (!isReadonly) {
console.log('执行effect时会取值','收集effect');
track(target, TrackOpTypes.GET, key);
}
//...
}
}
- 建立各种依赖储存到WeakMap中
// packages/reactivity/src/effect.ts
// 搜集某个对象的属性,对应的effect函数,存储数据结构为:
// weakMap:{name:'zf',age:12} => map
// map: {name => set(effect, effect), age => set(effect)}
const targetMap = new WeakMap();
/**
* 依赖搜集函数
* @param target 目标对象
* @param type 搜集的类型
* @param key 搜集的key
*/
export function track(target, type, key) {
// 如果当前属性没有在effect中应用,则不需要搜集
if (activeEffect === undefined) {
return;
}
// 1.第一层weakmap存储目标对象 》 map
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, depsMap = new Map);
}
// 2.第二层map存储key 》 set
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, dep = new Set);
}
// 3.第三层set储存activeEffect
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
}
}
4.触发更新
- 对新增属性和修改属性做分类
// packages/reactivity/src/baseHandlers.ts
function createSetter(shallow = false) {
return function set(target, key, value, receiver) {
// 先获取老的值
const oldValue = target[key];
// 判断是新增还是更新的标志量:
// 如果是数组就判断数组索引是否小于当前长度,小于则是修改,大于等于则是新增
// 如果是对象就判断对象是否包含key,包含则修改,不包含则新增
let hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key);
// 更新值
const result = Reflect.set(target, key, value, receiver);
if (!hadKey) {
// 新增
trigger(target, TriggerOrTypes.ADD, key, value);
} else if (hasChanged(oldValue,value)) {
// 修改 - 只有老值和新值不一样的时候才修改
trigger(target, TriggerOrTypes.SET, key, value, oldValue)
}
return result;
}
}
- 将需要触发的effect找到依次执行
// packages/reactivity/src/effect.ts
import { isArray, isIntegerKey } from "@vue/shared/src";
import { TriggerOrTypes } from "./operators";
/**
* 触发更新函数
* @param target 目标对象
* @param type 操作类型
* @param key 操作的key
* @param newValue 新值
* @param oldValue 老值
*/
export function trigger(target, type, key?, newValue?, oldValue?) {
// [1, 2, 3, 1] 0 "3" 1 undefined
console.log(target, type, key, newValue, oldValue);
// 如果这个属性没有 收集过effect,那不需要做任何操作
const depsMap = targetMap.get(target);
if (!depsMap) return;
// 用set存储偶有需要执行的effect,最终一起执行
const effects = new Set();
// 定义遍历effects的函数
const add = (effectsToAdd) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => effects.add(effect));
}
}
// 收集需要触发更新的依赖
if (key === 'length' && isArray(target)) {
// 如果修改的是数组的长度,需要特殊处理
depsMap.forEach((dep, key) => {
// 如果更改的是长度,或者 更改的长度小于收集的索引,也需要触发更新
if (key === 'length' || key > newValue) {
add(dep);
}
});
} else {
// 修改的是对象、不是数组的长度
if (key !== undefined) {
add(depsMap.get(key));
}
// 如果给数组添加了一个索引就触发长度的更新
switch (type) {
case TriggerOrTypes.ADD:
// 目标是数组,并且是是数组的索引
if (isArray(target) && isIntegerKey(key)) {
add(depsMap.get('length'));
}
break;
}
}
// 全部更新操作
effects.forEach((effect: any) => effect());
}
三、Refs实现
- ref 和 reactive的区别 reactive内部采用proxy,ref中内部使用的是defineProperty
0.使用方法
- shallowRef不会把对象的key变为响应式,ref可以,因为ref进行了reactive的迭代操作
- ref和shallowRef的使用
<div id="app"></div>
<script src="../node_modules/@vue/reactivity/dist/reactivity.global.js"></script>
<script>
let {effect, ref ,shallowRef} = VueReactivity;
// 将普通的类型 转化成一个对象,这个对象中有value属性 指向原来的值
let test = ref('test');
let state = ref({name: 'name'});
let shallow = shallowRef({name: 'name'});
effect(() => {
// // track
app.innerHTML = test.value + ' ' + state.value.name + ' ' + shallow.value.name;
});
setTimeout(() => {
test.value = 'hello'; // trigger
state.value.name = 'world'; // trigger
shallow.value.name = 'world'; // 不会trigger
}, 1000);
</script>
- toRef和toRefs的使用
<div id="app"></div>
<script src="../node_modules/@vue/reactivity/dist/reactivity.global.js"></script>
<script>
let {effect, reactive, toRef ,toRefs} = VueReactivity;
// 将普通的类型 转化成一个对象,这个对象中有value属性 指向原来的值
let proxy = reactive({name: 'test', age: 10});
// 将对象的全部属性转化为响应式
let {name, age} = toRefs(proxy);
let proxy2 = reactive({name: 'test', age:10});
let name2 = toRef(proxy2, 'name');
effect(() => {
// track
app.innerHTML = name.value + age.value + name2.value;
});
// 赋值》更新操作
setTimeout(() => {
name.value = 'hello'; // trigger
name2.value = 'hello'; // trigger
}, 1000);
</script>
1.入口文件
// packages/reactivity/src/index.ts
export {
ref,
shallowRef,
toRef,
toRefs
} from './ref'
2.ref和shallowRef
// packages/reactivity/src/ref.ts
import { isObject, hasChanged, isArray } from "@vue/shared/src";
import { reactive } from "./reactive";
import { track, trigger } from "./effect";
import { TrackOpTypes, TriggerOrTypes } from "./operators";
// 转换函数
const convert = (val) => isObject(val) ? reactive(val) : val;
// ref 和 shallowRef实现的基类
class RefImpl {
public _value; // 表示 声明了一个_value属性 但是没有赋值
public __v_isRef = true; // 产生的实例会被添加 __v_isRef 表示是一个ref属性
// 参数中前面增加修饰符 标识此属性放到了实例上
constructor(public rawValue, public shallow) {
// 如果是深度 需要把里面的都变成响应式的
this._value = shallow ? rawValue : convert(rawValue);
}
// 取value值,会代理到_value上
get value() {
// 收集依赖
track(this, TrackOpTypes.GET, 'value');
return this._value;
}
// 设置值
set value(newValue) {
// 判断老值和新值是否有变化
if (hasChanged(this._value, newValue)) {
this.rawValue = newValue;
this._value = this.shallow ? newValue : convert(newValue);
// 触发更新
trigger(this, TriggerOrTypes.SET, 'value', newValue);
}
}
}
function createRef(rawValue, shallow = false) {
return new RefImpl(rawValue, shallow);
}
// 导出方法
export function ref(value) {
return createRef(value);
}
export function shallowRef(value) {
return createRef(value, true);
}
3.toRef和toRefs
// packages/reactivity/src/ref.ts
// toRef 实现的基类
class ObjectRefImpl {
public __v_isRef = true;
constructor(public target, public key) {}
// 取值
get value(){
// 如果原对象是响应式的就会依赖收集
return this.target[this.key];
}
// 设值值
set value(newValue){
// 如果原来对象是响应式的 那么就会触发更新
this.target[this.key] = newValue;
}
}
// 将某一个对象或数组对应的key转化成ref
export function toRef(target, key) {
return new ObjectRefImpl(target, key);
}
// 将一个对象或数组的所有属性转化为red
export function toRefs(object){
const ret = isArray(object) ? new Array(object.length) : {};
for(let key in object){
ret[key] = toRef(object, key);
}
return ret;
}
四、工具类
// packages/shared/src/index.ts
// 判断是否为对象
export const isObject = (value) => typeof value == 'object' && value !== null;
// 合并方法
export const extend = Object.assign
// 判断数组
export const isArray = Array.isArray;
// 判断function
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;