变化
-
vue3 monorepo 模块拆分
-
tree-shaking
-
vue3: proxy vue2: defineProperty
-
Vue3: 对模板编译进行了优化, 编译时生成了 Block Tree, 可以对子节点的动态节点进行收集, 可以减少比较,并且采用 patchFlag 标记动态节点
-
vue3 采用 compositionApi 进行组织功能, 解决反复横跳, 优化服用逻辑,类型推断更加方便
-
增加 Fragment, Teleport, Suspense 组件
reactivity
- reactive
- shallowReactive
- readonly
- shallowReadonly
// 是不是仅读,是不是深度, 柯里化, new Proxy() 最核心的需要拦截数据的读取和数据的修改
function createReactiveObject(target, isReadonly, baseHandlers) {
// 如果目标不是对象,没法拦截, reactive 只能拦截对象
// 如果某个对象已经被代理了, 就不要再次代理了。 可能一个对象, 被代理是深度 又被仅读代理了
// 有两个缓存 reactiveMap readonlyMap
}
拦截 getter 触发收集依赖。逻辑在 baseHandlers
// 是不是仅读的, 是不是深度的
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
//1. 特殊属性的访问 __v_raw __v_isReadonly
//2. 是不是访问的数组的几个特殊方法。两类
//3. 是不是内置 Symbol 和 原型链上的属性
//4. 是不是 ref
const res = Reflect.get(target, key, receiver)
if(!isReadonly) {
// 收集依赖,等会数据变化后,更新对应的试图
track(target,TrackOpType key)
}
if(shallow) {
return res
}
if(isObject(res)) {// vue2 是一上来就递归, vue3 是当取值时,才去代理
return isReadonly ? readonly(res): reactive(res)
}
return res
}
}
effect 和 依赖收集
effect(() => { // 默认执行时会进行取值操作, 只要取值就会调用 get 方法,我们可以将对应的 effect 函数存放起来
app.innerHTML = state.name + state.age
})
export function effect(fn, options) {
const effect = createReactiveEffect(fn, options)
if(!opions.lazy) {
effect()
}
return effect;
}
let uid = 0;
let activeEffect; // 存储当前的 effect
const effectStack = [];
function createReactiveEffect(fn, options) {
const effect = function reactiveEffect() {
if(effectStack.includes(effect)) {
try{
effectStack.push(effect);
activeEffect = effect;
return fn(); // 函数执行时会取值, 会走get 方法
}finally{
// fn 执行完,effect 出栈
effectStack.pop(); //
// 重新设置当前 effect
activeEffect = effectStack[effectStack.length - 1]
}
}
}
effect.id = uid++; //
effect._isEffect = true; // 标识是 effect 函数
effect.raw = fn; // 保留 effect 对应的原函数
effect.options = options;// 在 effect 上保存用户的属性
return effect
}
// 问题 1
effect(() => { // effect1
state.name ==> effect1
effect(() => { // effect2
state.age ==> effect2
})
state.hobby ==> effect1
})
// 问题2
effect(() => {
state.age++ // age++ 会执行 effect, effect 执行会触发 state.age++ 为防止这种死循环,只有 effect 不在 effectStack 中的时候才入栈并执行
})
track
const targetMap = new WeakMap();
export function track(target, type, key) {
// 某个对象 =》 某个属性 =》 某个effect
if(activeEffect === undefined) {return};
let depsMap = targetMap.get(target);
if(!depsMap) {
targetMap.set(target, depsMap = new Map())
}
let deps = depsMap.get(key);
if(!deps) {
depsMap.set(key, deps = new Set())
}
if(!deps.has(activeEffect)) {
deps.add(activeEffect) ;// 不用 Set, activeEffect 会有重复吗
}
}
// 问题3
effect(() => { state.name + state.name // 如果 dep 不用 Set 的话, effect 会收集多次 })
effect 特殊处理
- effect 后返回的函数,再次被effect 是一个新的函数。
const onEffect = () => {};
const effect1 = effect(onEffect);
const effect2 = effect(effect1);
effect1 === effect2 // false
因为会判断是否时 __v_effect, 然后取出原函数后,再次重新 createEffect
- 每次重新执行 effect 都会取值, 那么都会调用get 方法,重新收集依赖
effect.deps = [name, age]
cleanup(); effect.deps.length = 0;
effect.deps 就是为了清除收集的。
const state = reactive({name: 'ming', age: 1})
effect(() => {
if(state.name === 'ming') {
console.log(state.age)
}
})
state.age = 100
state.name = 'hong' // 后面就不收集 age 了
state.age = 200
- 如果 effect 的选项中有 scheduler ,则会去执行 sheduler
effect 函数如果有额外的函数会优先执行顺序为: effect.options.onTriggler() --> effect.options.sheduler() --> effect()
effect(() => {
state.name
}, {
scheduler(effect) {
console.log('更新')
}
})
拦截 setter 触发更新
function createSetter(shallow = false) {
return function set(target, key, value, receiver) {
// 获取老值
const oldValue = target[key]; //
//1. 设置的值是 响应式的或者 ref 的
// const proxy = reactive({name: 'ming', age: ref(11)})
// proxy.name = reactive({c: 'cc'})
if(!shallow) {
value = toRaw(value); // 如果设置的值是 reactive 过的,会被转化成普通对象
if(!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value; // 老值是 ref ,新值不是 ref
return true
}
}
// 通过判断修改的这个 key 是否存在,来判断是走 新增逻辑还是走更新逻辑
const hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key);
if(!hadKey) {
// 新增
trigger(target, TriggerOpTypes.ADD, key, value)
}else if(hasChanged(oldValue, value)) {// 更新时判断新老值是否有变化, 有变化才触发更新
// 修改
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
}
// 找属性对应的 effect, 让他执行
export function trigger(target, type, key, newValue, oldValue) {
// 如果这个属性没有收集过 effect, 那就不需要做任何操作
const depsMap = targetMap.get(target);
if(!depsMap) { return }
const effects = new Set(); // 保存要更新的 effect
// 将所有的要执行的 effect 全部存到一个新的集合, 最终一起执行
const add = (effectsToAdd) => {
if(effectsToAdd) { // effectsToAdd 就是 deps 是一个 effect Set
effectsToAdd.forEach(effect => effects.add(effect))
}
}
// 1. 看修改的是不是数组的长度, 因为修改长度影响较大
if(key === 'length' && isArray(target)) {
// 如果对应的长度有依赖收集需要更新,
depsMap.forEach((deps, depkey) => {
if(depkey === 'length' || depkey > newValue ) { // 如果更改的长度小于收集的索引, 那么这个索引也需要触发 effect 重新执行
// state.arr[2] 收集的索引是 2
// state.arr.length = 1 // 更改的长度是 1
add(deps) // 一个 effect 中 state.arr[1] 收集, state.arr[2] 又收集一次
}
})
}else {
// 可能是对象
if(key !== undefined) {
add(depsMap.get(key))
}
// 如果改的是数组中的索引
switch(type) {
case TriggerOpTypes.ADD:
if(isArray(target) && isIntegerKey(key)) {
// 感觉有点问题, 如果收集的是索引 1 , 但是改的是索引 100,压根没有收集 length,
add(depsMap.get('length'))
}
}
}
effects.forEach((effect) => effect())
}
ref 、 shallowRef
ref 和 reactive 的区别 reactive 内部是 Proxy 实现, ref 是 defineProperty 实现的
function createRef(rawValue, shallow = false) {
return new RefImpl(rawValue, shallow)
}
const convert = val => isObject(val) ? reactive(val) : val;
class RefImpl {
public _value; // 声明一个属性, 但没有赋值
public __v_isRef = true; // 产生的实例会被添加 __v_isRef 表示是 ref 属性
constructor(public rawValue, public shallow) {
//
this._value = this.shallow ? rawValue : convert(rawValue);
}
// 类的属性访问器
get value() { // 这个 value 是 key
track(this,TrackOpTypes.GET, 'value');
return this._value;
}
set value(newValue) { // 这个 value 是 key
if(hasChanged(newValue, this.rawValue)) {
this._value = newValue;
this.rawValue = this.shallow ? newValue : convert(newValue);
trigger(this, TriggerOpTypes.SET, 'value', newValue)
}
}
}
toRef, toRefs
export function toRef(target, key) {
return new ObjectRefImpl(target, key)
}
// ObjectRefImpl 类就是代理一下
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
}
}
export function toRefs(target){
const ret = isArray(target) ? new Array(target.length) : {};
for(let key in target) {
ret[key] = toRef(target, key)
}
return ret
}
const state = reactive({name: 'ming}); const r1 = toRef(state, 'name')
r1.name.value 等价于 state.name
Reflect
- Reflect.ownKeys(obj) // 对象上所有的 key,包括 Symbol 类型的 key
- Reflect.get
- Reflect.set
- Reflect.apply(fn, null, [1,2]) // Function.prototype.apply.call(fn, null, [1,2])
Symbol
- 独一无二的值
const a1 = Symbol('a');
const a2 = Symbol('a');
console.log(a1 === a2);// false
const s1 = Symbol.for('kk'); // 声明全新的
const s2 = Symbol.for('kk')// 把之前声明的拿过来用
console.log(s1 === s2); // true
- 元编程能力,可以改写语法本身
const obj1 = {
[Symbol.toStringTag]: 'Hello'
}
Object.prototype.toString.call(obj1); // [object Hello]
// 隐式类型转换
const obj2 = {
[Symbol.toPrimitive](type) {
return '自定义值'
}
}
// 修改 instanceOf
const instance = {
[Symbol.hasInstance]() {
return '自定义值'
}
}
copy
-
浅拷贝 ... 展开符 和 Object.assign 一样都是处理对象的第一层
-
深拷贝
// 通过 weakMap 控制循环引用,记录拷贝前和拷贝后的对应关系, 拷贝过的对象, 不需要再次拷贝
function deepColone(obj, hash = new WeakMap) {
if(obj == null) return obj; // null undefined
if(obj instanceof RegExp) {return new RegExp(obj)}
if(obj instanceof Date) {return new Date(obj)}
//...
if(typeof obj !== 'objecct) {return obj}
if(hash.has(obj)) {
return hash.get(obj)
}
const copy = new obj.constructor();
hash.set(obj, copy); // 这一行不能放到 for 循环后,因为如果有循环引用的话,就走不到后面了
for(let key in obj) {
if(obj.hasOwnProperty(key)) {
copy[key] = deepClone(obj[key],hash)
}
}
return copy;
}
compose
function sum(a,b) {return a+ b}
function len(str) {return str.length}
function prefix(str) {return '$'+ str}
const finnal = compose(prefix, len,sum)
const r = finnal('a','b')
function compose(...fns) {
return function(...args) {
const pre = fns.pop()
return fns.reduceRight((r, next) => {
return next(r)
}, pre(...args))
}
}
function compose(...fns) {
return fns.reduceRight((pre, next) => {
return function(...args) {
return next(pre(...args)) // len(sum(...ars))
}
})
}
reduce 也一样
被 readonly 代理后不能被 reactive 代理了
被 reactive 代理后可以被 readonly 代理
被 reactive 代理后, 不能重复被 reactive 代理了, 直接返回了
带有 __v_skip 属性的不代理
toRaw
function toRaw(proxy){
return (proxy && toRaw(proxy.__v_raw)) || proxy
}
markRaw
functopn markRaw(obj) {
def(obj, '__v_skip', true)
}
数组的额外处理
// ['a', 'b', 'c'].includes(x); //
const arrayInstrumentation = {}
['includes', 'indexOf', 'lastIndexOf'].forEach(key => {
const method = Array.prototype[key];
arrayInstrumentation[key] = function(this, ...args) {
// reactive([1,2]).includes('1') // 被代理后的数组
const arr = toRaw(this)
for(let i = 0, l = this.length; i< l; i++) {
track(arr, TrackOpTypes.GET, i + '')
}
const res = method.apply(arr, args)
if(res === -1 || res === false) {
return method.apply(arr, args.map(toRaw));// ['a'].includes(ref('a')) // true
}else {
return res
}
}
})
watchEffect(() => {
arr.push(1)
})
watchEffect(() => {
arr.push(2)
})
如果不特殊处理会无限循环
['push','pop', 'shift','unshift','splice'].forEach(key => {
const method = Array.prototype[key];
arrInstrumentations[key] = function(this, ...args) {
pauseTracking();
const res = method.apply(this, args)
resetTracking();
return res;
}
})
内置 Symbol 或者是原型链上查找的也直接返回
if(isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res;
}
解包
const r = reactive({a: ref(2)})
r.a === 2 // 此时不需要有 value; // 会解包
const arr = [ref(2), 3, 4]
arr[0].value === 2 // 此时需要带有 value
if(isRef) {
// 对于数组取值不解包
const shouldUnwrap = !targetIsArray || !isIntegerKey(key);
return shouldUnwrap ? res.value : res;
}
流程梳理
- reactive
- 判断是不是对象
- 重复代理的情况
- 对不同类型进行 proxy
- 做缓存
- get baseHandler
- 特殊属性处理
__v_raw__v_isReadonly - 是否是数组的特殊方法
- Symbol 内置属性和原型链属性不处理
- 对 ref 进行处理,对象的属性值是 ref 会解包,数组的元素是 ref 不会解包
- 特殊属性处理
- set baseHandler
- 设置值的时候对 ref 单独处理,target 不是数组, 老值是 ref ,新值不是 ref, oldValue.value = value
- 对新增和修改做不同的处理
- track 就是维护属性和 effect 之间的关系 {target: {name: [effect, effecct], age: [effect]}}
- trigger 就是找到 target 中对应的属性的 effect 列表,然后执行 effect
- 响应式原理: 属性会收集 effect, 属性更新会让收集的 effect 重新执行
------ @vue/compiler-sfc
| | |
vue ---- @vue/compiler-dom ---> @vue/compiler-core
|
|__ @vue/runtime-dom --> @vue/runtime-core --> @vue/reactivity
@vue/runtime-core 是和平台无关的