響應性基礎 API 之 reactive
基本用法
- 返回對象的響應式副本,只能代理對象,不能代理普通值
const obj = reactive({count: 0, name: 'hyh'})
const count = reactive(1)
// value cannot be made reactive: 1
實現原理
reactive
是基於 Proxy
實現的響應式。
const ReactiveFlags = {
SKIP = '__v_skip',
IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly',
RAW = '__v_raw'
}
// 用於緩存代理過的對象
const proxyMap = new WeakMap();
function createArrayInstrumentations(){
const instrumentations = {};
// proxy([a,b,c]).includes('a')
;['includes', 'indexOf', 'lastIndexOf'].forEach(key =>{
instrumentations[key] = function(this, ...args){
// 將代理對象轉化成普通的數組,然后進行依賴收集
const arr = toRaw(this)
for(let i = 0, l = this.length; i < l; i++ ){
// 依賴收集
track(arr, 'get', i + '')
}
// 調用數組原來的方法
const res = arr[key](...args)
// we run the method using the original args first (which may be reactive)
if(res === -1 || res === false){
// 如果沒有找到 將參數轉化為原始類型重新執行
/*
let obj = {a: 1};
let reactObj = reactive(obj)
const arr1 = reactive([obj])
console.log('arr1.includes(obj) :>> ', arr1.includes(reactObj)); == true
*/
return arr[key](...args.map(toRaw))
}else{
return res
}
}
})
// instrument length-altering mutation methods to avoid length being tracked
// which leads to infinite loops in some cases (#2137)
;['push', 'pop', 'shift', 'unshift', 'splice'].forEach(key => {
instrumentations[key] = function(this, ...args){
pauseTracking();
const res = toRaw(this).apply(this, args)
resetTracking();
return res
}
})
}
function createGetter(isReadonly = false, shallow = false){
return function get(target, key, receiver){
// 判斷代理對象是否為數組
const targetIsArray = isArray(target);
if(targetIsArray && hasOwn(arrayInstrumentations, key)){
// 如果是特殊方法,沒有進行依賴收集, 而是重寫了一些方法
return Reflect.get(arrayInstrumentations, key, receiver)
}
// ...對象處理
}
}
function createSetter(shallow = false){
return function set(target, key, value, receiver){
const oldValue = target[key]
// 判斷:是新增還是修改
const hadKey = isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
const res = Reflect.set(target, key, value, receiver);
// 觸發effect更新
if(!hadKey){
trigger(target, 'add', key, value)
}else{
trigger(target, 'set', key, value, oldValue)
}
}
}
function mutableHandler(){
get: createGetter(),
set: cerateSetter(),
}
function reactive(target){
// 針對特殊場景 reactive(readonly(obj))-> 直接返回readonly即可
if(target && target[ReactiveFlags.IS_READONLY]){
return target
}
return createReactiveObject(target, false, mutaleHandler)
}
function createReactiveObject(target, isReadonly, baseHandler){
if(!isObject(target)){
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// target already has corresponding Proxy
const existingProxy = proxyMap.get(target) // 看目標是否被代理過
if (existingProxy) { // 如果代理過,直接返回代理過的對象
return existingProxy
}
const proxy = new Proxy( // 創建一個proxy對象
target,// 調用對應的handler進行代理
targetType: baseHandlers
)
proxyMap.set(target, proxy) // 存到緩存中
return proxy
}
以上代碼只是Vue3的部分源碼,簡單的實現了reactive的響應式原理。