vue3中reactive和ref的實現(xiàn)與區(qū)別詳解
前言
reactive和ref都是vue3實現(xiàn)響應式系統(tǒng)的api,他們是如何實現(xiàn)響應式的呢?reactive和ref又有什么區(qū)別呢,看完這篇文章,相信答案就呼之欲出了。
effect
vue3中的reactive實現(xiàn)了數(shù)據(jù)的響應式,說到數(shù)據(jù)響應式離不開effect,effect是關鍵,effect()方法是暴露給創(chuàng)作者的一種方法,參數(shù)1是一個回調函數(shù),寫在回調函數(shù)的代碼可以用來模擬setup執(zhí)行函數(shù)的響應式數(shù)據(jù)被寫在template模板的數(shù)據(jù),當數(shù)據(jù)更新之后,將effect中的回調函數(shù)再次調用,達到數(shù)據(jù)更新便更新視圖的目的。也可以把effect叫做數(shù)據(jù)相關依賴,即記錄用到響應式數(shù)據(jù)的代碼。參數(shù)二是一個對象,里面有l(wèi)azy屬性用來規(guī)定effect的回調函數(shù)第一次是否執(zhí)行。
<body>
<div id="app">hello</div>
<script>
let data={
name:'草原一匹狼'
}
effect(()=>{
let app = document.getElementById('app')
app.innerHTML=data.name
},{lazy:true})
setTimeout(()=>{
data.name='切'
},1000)
</script>
</body>
實現(xiàn)effect方法,保存回調函數(shù)當數(shù)據(jù)更新再次調用回調函數(shù)達到更新視圖
export function effect(fn, options: any = {}) {//更新視圖也就是吧視圖用到數(shù)據(jù)在執(zhí)行一遍
const effect = createReactEffect(fn, options);
if (!options.lazy) {//如果lazy:false執(zhí)行
effect();
}
return effect;
}
let uid = 0;
//每次調用都會創(chuàng)建一個effect
let activeeffect;//設置當前effect為全局變量好進行收集
let effectStack = [];//保存effect 因為可能會有多個effect
function createReactEffect(fn, options) {
const effect = function reactiveEffect() {
if (!effectStack.includes(effect)) {
try {
effectStack.push(effect);
activeeffect = effect;
fn();
} finally {
effectStack.pop();
activeeffect = effectStack[effectStack.length - 1];
}
}
};
effect.id = uid++; //區(qū)別effect
effect._isEffect = true; //區(qū)別effect 是不是響應式的effect
effect.raw = fn; //保存回調函數(shù)到自身
effect.options = options; //保存用戶屬性lazy
return effect;
}
reactive
了解完effect,接下來是實現(xiàn)reactive的具體過程:
- 響應式數(shù)據(jù)用proxy代理時get收集依賴
- 響應式數(shù)據(jù)用proxy代理時set執(zhí)行依賴
1.響應式數(shù)據(jù)用proxy代理時get收集依賴
function createGetter(isReadonly = false, shall = false) {
return function get(target, key, receiver) {
const res = Reflect.get(target, key, receiver); //taget[key]
if (!isReadonly) {
//判斷是不是只讀 收集依賴
Track(target, TackOpType.GET, key);//Track收集依賴 一個響應式屬性key對應一個effect保存起來
}
if (shall) {
//只代理一層
return res;
}
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res);
}
return res;
};
}
//收集effect 獲取數(shù)據(jù)觸發(fā)get 收集依賴數(shù)據(jù)變化更新視圖 key和effect11對應
let targetMap = new WeakMap(); //棧結構(target ,new Map(key(key effect一一對應),new Set(activeeffect)))
export function Track(target, type, key) {
//對應的key
if (activeeffect == undefined) {
return;
}
let depMAp = targetMap.get(target);
if (!depMAp) {
targetMap.set(target, (depMAp = new Map()));
}
let dep = depMAp.get(key);
if (!dep) {
depMAp.set(key, (dep = new Set()));
}
if (!dep.has(activeeffect)) {
dep.add(activeeffect);
}
}
2.響應式數(shù)據(jù)用proxy代理時set執(zhí)行依賴
function createSetter(shall = false) {
return function set(target, key, value, receive) {
const oldValue = target[key];
let hasKey =
Array.isArray(oldValue) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key);
const result = Reflect.set(target, key, value, receive); //獲取最新的值 對象數(shù)據(jù)已經(jīng)更新
if (!hasKey) {
// 新增
trigger(target, TriggerOpTypes.ADD, key, value);
} else {
// 修改數(shù)組
if (hasChange(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue);
}
}
return result;
};
}
//觸發(fā)更新依賴effect
export function trigger(target, type, key?, newValue?, oldValue?) {
const depsMap = targetMap.get(target);
let effectSet = new Set();
if (!depsMap) return;
const add = (effectAdd) => {
if (effectAdd) {
effectAdd.forEach((effect) => effectSet.add(effect));
}
};
add(depsMap.get(key)); //獲取當前屬性的effect
//處理數(shù)組
if (key == "length" && Array.isArray(target)) {
depsMap.forEach((dep, key) => {
if (key === "length" || key >= newValue) {
add(dep);
}
});
}else{
//處理對象
if(key !== undefined){
add(depsMap.get(key))
}
switch(type){
case TriggerOpTypes.ADD:
if(Array.isArray(target)&& isIntegerKey(key)){
add(depsMap.get('target'))
}
}
}
effectSet.forEach((effect: any) => {
if(effect.options.sch){
effect.options.sch(effect)
}else{
effect()
}
});
- 被reactive包裹的數(shù)據(jù)實現(xiàn)響應式,首先得effec保存回調函數(shù),方便數(shù)據(jù)更新再次調用來更新視圖,
- 當用proxy對對象代理時get方法中將視圖用到的代碼片段即相關effect與key一一對應保存起來
- 當數(shù)據(jù)改變用到proxy的set方法時,用key找到對應effect執(zhí)行
ref
相信大家都知道用reactive實現(xiàn)復雜數(shù)據(jù)代理用ref實現(xiàn)簡單數(shù)據(jù)的代理,因為vue用es6的proxy代理整個對象,而ref是給簡單數(shù)據(jù)用一個對象包裹起來用object.defineproperty方式代理并且用.value訪問,如果用ref對一個對象進行響應式處理則會調用crtoReactive方法再次對.value的屬性進行代理。 根據(jù)上面已經(jīng)實現(xiàn)的reactive api實現(xiàn)ref就簡單多了。
具體步驟為:
1.如果是簡單數(shù)據(jù)就用object.defineproperty代理
2.如果是復雜數(shù)據(jù)就用createReactive方法再次代理
export function ref(target){
return creatRef(target)
}
//創(chuàng)建實例對象
class RefImpl{
public _v_isRef=true
public _value
public _shallow
public _rawValue
constructor(public target,public shallow){
this._shallow=shallow
this._rawValue = shallow ? target : toRaw(target)
this._value = shallow ? target : toReactive(target)
}
get value(){
Track(this,TackOpType.GET,"value")
return this.value
}
set value(newVAlue){
if(newVAlue!==this._value)
this._rawValue=newVAlue
this._value = isObject(newVAlue) ? newVAlue : toReactive(newVAlue)//這是ref不失去響應式的關鍵如果新的值就重新代理
trigger(this,TriggerOpTypes.SET,"value",newVAlue)
}
}
function creatRef(target,shallow=false){
//創(chuàng)建ref實例對象
return new RefImpl(target,shallow)
}
到此這篇關于vue3中reactive和ref的實現(xiàn)與區(qū)別詳解的文章就介紹到這了,更多相關 vue3 reactive ref內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
vue+vuex+axios+echarts畫一個動態(tài)更新的中國地圖的方法
本篇文章主要介紹了vue+vuex+axios+echarts畫一個動態(tài)更新的中國地圖的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-12-12
vue-cli 自定義指令directive 添加驗證滑塊示例
本篇文章主要介紹了vue-cli 自定義指令directive 添加驗證滑塊示例,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-10-10
解決vue2中使用axios http請求出現(xiàn)的問題
下面小編就為大家分享一篇解決vue2中使用axios http請求出現(xiàn)的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-03-03
在vue框架下使用指令vue add element安裝element報錯問題
這篇文章主要介紹了在vue框架下使用指令vue add element安裝element報錯問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-10-10
Laravel 如何在blade文件中使用Vue組件的示例代碼
這篇文章主要介紹了Laravel 如何在blade文件中使用Vue組件,本文通過示例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-06-06
Vue中的transition封裝組件的實現(xiàn)方法
這篇文章主要介紹了Vue中的transition封裝組件的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-08-08

