您好,登錄后才能下訂單哦!
本篇內容介紹了“vue3中的reactive()怎么使用”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
調試版本為3.2.45
什么是reactive?
reactive是Vue3中提供實現響應式數據的方法.
在Vue2中響應式數據是通過defineProperty來實現的.
而在Vue3響應式數據是通過ES6的Proxy來實現的
reactive注意點
reactive參數必須是對象(json/arr)
如果給reactive傳遞了其他對象,默認情況下修改對象,界面不會自動更新,如果想更新,可以通過重新賦值的方式。
<script setup>
import {reactive} from 'vue'
const data = reactive({ //定義對象
name:'測試',
age:10
})
const num = reactive(1)//定義基本數據類型
console.log(data)//便于定位到調試位置
</script>
<template>
<div>
<h2>{{ data.name }}</h2>
</div>
</template>
<style scoped></style>
接下來我們可以開始調試了,設置好斷點后,只要重新刷新頁面就可以進入調試界面。
我們先調試簡單的基本數據類型
1.
/*1.初始進來函數,判斷目標對象target是否為只讀對象,如果是直接返回*/
function reactive(target) {
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) {
return target;
}
//創建一個reactive對象,五個參數后續會講解
return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}
/*2.判斷是來判斷target是否為只讀。*/
function isReadonly(value) {
return !!(value && value["__v_isReadonly" /* ReactiveFlags.IS_READONLY */]);
}
/*3.創建一個reactive對象*/
/*createReactiveObject接收五個參數:
target被代理的對象,
isReadonl是不是只讀的,
baseHandlers proxy的捕獲器,
collectionHandlers針對集合的proxy捕獲器,
proxyMap一個用于緩存proxy的`WeakMap`對象*/
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
//如果target不是對象則提示并返回
/*這里會跳轉到如下方法
判斷是否原始值是否為object類型
const isObject = (val) => val !== null && typeof val === 'object';
*/
if (!isObject(target)) {
if ((process.env.NODE_ENV !== 'production')) {
console.warn(`value cannot be made reactive: ${String(target)}`);
}
return target;
}
// 如果target已經是proxy是代理對象則直接返回.
if (target["__v_raw" /* ReactiveFlags.RAW */] &&
!(isReadonly && target["__v_isReactive" /* ReactiveFlags.IS_REACTIVE */])) {
return target;
}
// 從proxyMap中獲取緩存的proxy對象,如果存在的話,直接返回proxyMap中對應的proxy。否則創建proxy。
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy;
}
// 并不是任何對象都可以被proxy所代理。這里會通過getTargetType方法來進行判斷。
const targetType = getTargetType(target);
//當類型值判斷出是不能代理的類型則直接返回
if (targetType === 0 /* TargetType.INVALID */) {
return target;
}
//通過使用Proxy函數劫持target對象,返回的結果即為響應式對象了。這里的處理函數會根據target對象不同而不同(這兩個函數都是參數傳入的):
//Object或者Array的處理函數是collectionHandlers;
//Map,Set,WeakMap,WeakSet的處理函數是baseHandlers;
const proxy = new Proxy(target, targetType === 2 /* TargetType.COLLECTION */ ? collectionHandlers : baseHandlers);
proxyMap.set(target, proxy);
return proxy;
}
getTargetType
方法調用流程
//1.進入判斷如果value有__v_skip屬性且為true或對象是可拓展則返回0,否則走類型判斷函數
function getTargetType(value) {
//Object.isExtensible() 方法判斷一個對象是否是可擴展的(是否可以在它上面添加新的屬性)。
return value["__v_skip" /* ReactiveFlags.SKIP */] || !Object.isExtensible(value)
? 0 /* TargetType.INVALID */
: targetTypeMap(toRawType(value));
}
//2.這里通過Object.prototype.toString.call(obj)來判斷數據類型
const toRawType = (value) => {
// extract "RawType" from strings like "[object RawType]"
return toTypeString(value).slice(8, -1);
};
const toTypeString = (value) => objectToString.call(value);
//3.這里rawType是為'Object'所以會返回1
function targetTypeMap(rawType) {
switch (rawType) {
case 'Object':
case 'Array':
return 1 /* TargetType.COMMON */;
case 'Map':
case 'Set':
case 'WeakMap':
case 'WeakSet':
return 2 /* TargetType.COLLECTION */;
default:
return 0 /* TargetType.INVALID */;//返回0說明除前面的類型外其他都不能被代理,如Date,RegExp,Promise等
}
}
在createReactiveObject
方法中const proxy = new Proxy(target, targetType === 2 /* TargetType.COLLECTION */ ? collectionHandlers : baseHandlers);
這一條語句中,第二個參數判斷target是否為Map或者Set類型。從而使用不同的handler來進行依賴收集。
在調試的文件node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js
中,我們從reactive
函數的createReactiveObject
函數調用的其中兩個參數mutableHandlers
和mutableCollectionHandlers
開始往上查詢
mutableHandlers
的實現const mutableHandlers = {
get,// 獲取值的攔截,訪問對象時會觸發
set,// 更新值的攔截,設置對象屬性會觸發
deleteProperty,// 刪除攔截,刪除對象屬性會觸發
has,// 綁定訪問對象時會攔截,in操作符會觸發
ownKeys// 獲取屬性key列表
};
function deleteProperty(target, key) {
// key是否是target自身的屬性
const hadKey = hasOwn(target, key);
// 舊值
const oldValue = target[key];
// 調用Reflect.deleteProperty從target上刪除屬性
const result = Reflect.deleteProperty(target, key);
// 如果刪除成功并且target自身有key,則觸發依賴
if (result && hadKey) {
trigger(target, "delete" /* TriggerOpTypes.DELETE */, key, undefined, oldValue);
}
return result;
}
//
function has(target, key) {
//檢查目標對象是否存在此屬性。
const result = Reflect.has(target, key);
// key不是symbol類型或不是symbol的內置屬性,進行依賴收集
if (!isSymbol(key) || !builtInSymbols.has(key)) {
track(target, "has" /* TrackOpTypes.HAS */, key);
}
return result;
}
/*ownKeys可以攔截以下操作:
1.Object.keys()
2.Object.getOwnPropertyNames()
3.Object.getOwnPropertySymbols()
4.Reflect.ownKeys()操作*/
function ownKeys(target) {
track(target, "iterate" /* TrackOpTypes.ITERATE */, isArray(target) ? 'length' : ITERATE_KEY);
return Reflect.ownKeys(target);
}
get
方法實現const get = /*#__PURE__*/ createGetter();
/*傳遞兩個參數默認都為false
isReadonly是否為只讀
shallow是否轉換為淺層響應,即Reactive---> shallowReactive,shallowReactive監聽了第一層屬性的值,一旦發生改變,則更新視圖;其他層,雖然值發生了改變,但是視圖不會進行更新
*/
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
//1.是否已被reactive相關api處理過;
if (key === "__v_isReactive" /* ReactiveFlags.IS_REACTIVE */) {
return !isReadonly;
}
//2.是否被readonly相關api處理過
else if (key === "__v_isReadonly" /* ReactiveFlags.IS_READONLY */) {
return isReadonly;
}
else if (key === "__v_isShallow" /* ReactiveFlags.IS_SHALLOW */) {
return shallow;
}
//3.檢測__v_raw屬性
else if (key === "__v_raw" /* ReactiveFlags.RAW */ &&
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap).get(target)) {
return target;
}
//4.如果target是數組,且命中了一些屬性,則執行函數方法
const targetIsArray = isArray(target);
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver);
}
//5.Reflect獲取值
const res = Reflect.get(target, key, receiver);
//6.判斷是否為特殊的屬性值
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res;
}
if (!isReadonly) {
track(target, "get" /* TrackOpTypes.GET */, key);
}
if (shallow) {
return res;
}
//7.判斷是否為ref對象
if (isRef(res)) {
// ref unwrapping - skip unwrap for Array + integer key.
return targetIsArray && isIntegerKey(key) ? res : res.value;
}
//8.判斷是否為對象
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res);
}
return res;
};
}
檢測__v_isReactive
屬性,如果為true,表示target已經是一個響應式對象了。
依次檢測__v_isReadonly
和__v_isShallow
屬性,判斷是否為只讀和淺層響應,如果是則返回對應包裝過的target。
檢測__v_raw
屬性,這里是三元的嵌套,主要判斷原始數據是否為只讀或者淺層響應,然后在對應的Map里面尋找是否有該目標對象,如果都為true則說明target已經為響應式對象。
如果target是數組,需要對一些方法(針對includes
、indexOf
、lastIndexOf
、push
、pop
、shift
、unshift
、splice
)進行特殊處理。并對數組的每個元素執行收集依賴,然后通過Reflect獲取數組函數的值。
Reflect
獲取值。
判斷是否為特殊的屬性值,symbol
, __proto__
,__v_isRef
,__isVue
, 如果是直接返回前面得到的res
,不做后續處理;
如果為ref
對象,target
不是數組的情況下,會自動解包。
如果res
是Object
,進行深層響應式處理。從這里就能看出,Proxy
是懶惰式的創建響應式對象,只有訪問對應的key
,才會繼續創建響應式對象,否則不用創建。
set
方法實現例子:data.name='2'
const set = /*#__PURE__*/ createSetter();
//shallow是否轉換為淺層響應,默認為false
function createSetter(shallow = false) {
//1.傳遞四個參數
return function set(target, key, value, receiver) {
let oldValue = target[key];
//首先獲取舊值,如果舊值是ref類型,且新值不是ref類型,則不允許修改
if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
return false;
}
//2.根據傳遞的shallow參數,來執行之后的操作
if (!shallow) {
if (!isShallow(value) && !isReadonly(value)) {
oldValue = toRaw(oldValue);
value = toRaw(value);
}
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value;
return true;
}
}
//3.檢測key是不是target本身的屬性
const hadKey = isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key);
//利用Reflect.set()來修改值,返回一個Boolean值表明是否成功設置屬性
//Reflect.set(設置屬性的目標對象, 設置的屬性的名稱, 設置的值, 如果遇到 `setter`,`receiver`則為`setter`調用時的`this`值)
const result = Reflect.set(target, key, value, receiver);
// 如果目標是原始原型鏈中的某個元素,則不要觸發
if (target === toRaw(receiver)) {
//如果不是target本身的屬性那么說明執行的是'add'操作,增加屬性
if (!hadKey) {
trigger(target, "add" /* TriggerOpTypes.ADD */, key, value);
}
//4.比較新舊值,是否觸發依賴
else if (hasChanged(value, oldValue)) {
//5.觸發依賴
trigger(target, "set" /* TriggerOpTypes.SET */, key, value, oldValue);
}
}
return result;
};
}
1、以data.name='2'
這段代碼為例,四個參數分別為:
target
:目標對象,即target={"name": "測試","age": 10}
(此處為普通對象)
key
:修改的對應key,即key: "name"
value
:修改的值,即value: "2"
receiver
:目標對象的代理。即receiver=Proxy {"name": "測試","age": 10}
2、shallow
為false的時候。
第一個判斷:如果新值不是淺層響應式并且不是readonly,新舊值取其對應的原始值。
第二個判斷:如果target不是數組并且舊值是ref類型,新值不是ref類型,直接修改oldValue.value為value
3.檢測key
是不是target本身的屬性。這里的hadKey
有兩個方法,isArray
就不解釋,就是判斷是否為數組
isIntegerKey
:判斷是不是數字型的字符串key值
//判斷參數是否為string類型,是則返回true
const isString = (val) => typeof val === 'string';
//如果參數是string類型并且不是'NaN',且排除-值(排除負數),然后將 key 轉換成數字再隱式轉換為字符串,與原 key 對比
const isIntegerKey = (key) => isString(key) &&
key !== 'NaN' &&
key[0] !== '-' &&
'' + parseInt(key, 10) === key;
4.比較新舊值,如果新舊值不同,則觸發依賴進行更新
hasChanged
方法
//Object.is()方法判斷兩個值是否是相同的值。
const hasChanged = (value, oldValue) => !Object.is(value, oldValue);
5.觸發依賴,這里太過復雜,筆者也沒搞懂,如果有興趣的讀者可自行去調試
<script setup>
import { reactive } from "vue";
const data = reactive({
name: "測試",
age: 10,
});
data.name='1'//這里并未收集依賴,在處理完 createSetupContext 的上下文后,組件會停止依賴收集,并且開始執行 setup 函數。具體原因有興趣的讀者可以自行去了解
const testClick = ()=>{
data.name='test'
}
</script>
<template>
<div>
<h2>{{ data.name }}</h2>
<el-button @click="testClick">Click</el-button>
</div>
</template>
<style scoped></style>
const num = reactive(2)
這里比較簡單,在createReactiveObject
函數方法里面:
if (!isObject(target)) {
if ((process.env.NODE_ENV !== 'production')) {
console.warn(`value cannot be made reactive: ${String(target)}`);
}
return target;
}
因為判斷類型不是對象,所以會在控制臺打印出警告,并且直接返回原數據
<script>
const data = reactive({
name: "測試",
age: 10,
});
const num = reactive(data)//定義一個已經是響應式對象
</script>
1.調試開始進來reactive
函數,然后會經過isReadonly
函數,這里跟前面不同的是,target是一個proxy對象,它已經被代理過有set
,get
等handler。所以在isReadonly
函數讀取target
的時候,target
會進行get
函數的讀取操作。
function reactive(target) {
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) {
return target;
}
return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}
2.可以看到get
傳入的參數有個key="__v_isReadonly"
,這里的isReadonly
返回是false,接下來進入createReactiveObject
函數
這里說明下,在本次調試中常見的vue里面定義的私有屬性有:
__v_skip
:是否無效標識,用于跳過監聽
__v_isReactive
:是否已被reactive相關api處理過
__v_isReadonly
:是否被readonly相關api處理過
__v_isShallow
:是否為淺層響應式對象
__v_raw
:當前代理對象的源對象,即target
3.在createReactiveObject
函數中,經過target["__v_isReactive"]
的時候會觸發target
的get函數,這時候get
函數傳入的參數中key='__v_raw'
if (target["__v_raw" /* ReactiveFlags.RAW */] &&
!(isReadonly && target["__v_isReactive" /* ReactiveFlags.IS_REACTIVE */])) {
return target;
}
由上圖可知我們檢測target
即已定義過的proxy對象,被reactive
api處理過就會有__v_raw
私有屬性,然后再進行receiver
的判斷,判斷target
是否為只讀或淺層響應。如果都不是則從緩存proxy的WeakMap
對象中獲取該元素。最后直接返回target
的原始數據(未被proxy代理過)。
最后回到之前的判斷,由下圖可知,target
的__v_raw
屬性存在,isReadonly
為false,__v_isReactive
的值為true,可以說明reactive
函數需要處理的對象是一個被reactive
API處理過的對象,然后直接返回該對象的原始數據。
經過ref
函數處理,其本質也是一個對象,所以使用reactive
函數處理ref
類型就跟處理復雜數據類型一樣過程。
(開發中應該不會有這種嵌套行為吧,這里只是為了測試多樣化)。
<script setup>
import { reactive,ref } from "vue";
const data = reactive({
name: "測試",
age: 10,
});
const numRef = ref(1)
const dataRef = ref({
name: "測試2",
age: 20,
})
const num = reactive(numRef)
const dataReactive = reactive(dataRef)
console.log('data',data)
console.log('numRef',numRef)
console.log('num',num)
console.log('dataRef',dataRef)
console.log('dataReactive',dataReactive)
</script>
Map
類型是鍵值對的有序列表,而鍵和值都可以是任意類型。
Set
和Map
類似,也是一組key的集合,但不存儲value。由于key不能重復,所以,在Set
中,沒有重復的key。
<script setup>
import { reactive } from "vue";
const mapData = new Map();
mapData.set('name','張三')
const setData = new Set([1,2,3,1,1])
console.log(mapData)
console.log(setData)
const mapReactive = reactive(mapData)
console.log(mapReactive)
</script>
由上圖可知Map結構和Set結構使用typeof
判斷是object
,所有流程前面會跟復雜數據類型一樣,知道在createReactiveObject
函數的getTargetType()
函數開始不同。
在getTargetType
函數里面toRawType()
判斷數據類型所用方法為Object.prototype.toString.call()
const targetType = getTargetType(target);
function getTargetType(value) {
return value["__v_skip" /* ReactiveFlags.SKIP */] || !Object.isExtensible(value)
? 0 /* TargetType.INVALID */
: targetTypeMap(toRawType(value));
}
function targetTypeMap(rawType) {//rawType="Map",這里返回值為2
switch (rawType) {
case 'Object':
case 'Array':
return 1 /* TargetType.COMMON */;
case 'Map':
case 'Set':
case 'WeakMap':
case 'WeakSet':
return 2 /* TargetType.COLLECTION */;
default:
return 0 /* TargetType.INVALID */;
}
}
這時候targetType=2
,在createReactiveObject
的函數中const proxy = new Proxy(target, targetType === 2 /* TargetType.COLLECTION */ ? collectionHandlers : baseHandlers);
的三元表達式中可得知,這里的handler
為collectionHandlers
。
網上查找可在reactive
函數中return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
這條語句找到,當rawType=1
時handler
是用mutableHandlers
,rawType=1
時是用mutableCollectionHandlers
。
mutableCollectionHandlers
方法:
const mutableCollectionHandlers = {
get: /*#__PURE__*/ createInstrumentationGetter(false, false)
};
//解構createInstrumentations
const [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations, shallowReadonlyInstrumentations] = /* #__PURE__*/ createInstrumentations();
//傳入兩個參數,是否為可讀,是否為淺層響應
function createInstrumentationGetter(isReadonly, shallow) {
const instrumentations = shallow
? isReadonly
? shallowReadonlyInstrumentations
: shallowInstrumentations
: isReadonly
? readonlyInstrumentations
: mutableInstrumentations;
return (target, key, receiver) => {
if (key === "__v_isReactive" /* ReactiveFlags.IS_REACTIVE */) {
return !isReadonly;
}
else if (key === "__v_isReadonly" /* ReactiveFlags.IS_READONLY */) {
return isReadonly;
}
else if (key === "__v_raw" /* ReactiveFlags.RAW */) {
return target;
}
return Reflect.get(hasOwn(instrumentations, key) && key in target
? instrumentations
: target, key, receiver);
};
}
//篇幅問題以及這方面筆者并未深入,所以就大概帶過
function createInstrumentations() {
//創建了四個對象,對象內部有很多方法,其他去掉了,完整可自行去調試查看
const mutableInstrumentations = {
get(key) {
return get$1(this, key);
},
get size() {
return size(this);
},
has: has$1,
add,
set: set$1,
delete: deleteEntry,
clear,
forEach: createForEach(false, false)
};
.................
//通過createIterableMethod方法操作keys、values、entries、Symbol.iterator迭代器方法
const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator];
iteratorMethods.forEach(method => {
mutableInstrumentations[method] = createIterableMethod(method, false, false);
readonlyInstrumentations[method] = createIterableMethod(method, true, false);
shallowInstrumentations[method] = createIterableMethod(method, false, true);
shallowReadonlyInstrumentations[method] = createIterableMethod(method, true, true);
});
return [
mutableInstrumentations,
readonlyInstrumentations,
shallowInstrumentations,
shallowReadonlyInstrumentations
];
}
“vue3中的reactive()怎么使用”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。