Vue 進階系列丨實現簡易reactive和ref

a477a844fff6dfdf4a7830aa483224d9.png

Vue?進階系列教程將在本號持續發布,一起查漏補缺學個痛快!若您有遇到其它相關問題,非常歡迎在評論中留言討論,達到幫助更多人的目的。若感本文對您有所幫助請點個贊吧!

2013年7月28日,尤雨溪第一次在 GItHub 上為 Vue.js 提交代碼;2015年10月26日,Vue.js 1.0.0版本發布;2016年10月1日,Vue.js 2.0發布。

最早的 Vue.js 只做視圖層,沒有路由,?沒有狀態管理,也沒有官方的構建工具,只有一個庫,放到網頁里就可以直接用了。

后來,Vue.js 慢慢開始加入了一些官方的輔助工具,比如路由(Router)、狀態管理方案(Vuex)和構建工具(Vue-cli)等。此時,Vue.js 的定位是:The Progressive Framework。翻譯成中文,就是漸進式框架。

Vue.js2.0 引入了很多特性,比如虛擬 DOM,支持 JSX 和 TypeScript,支持流式服務端渲染,提供了跨平臺的能力等。Vue.js 在國內的用戶有阿里巴巴、百度、騰訊、新浪、網易、滴滴出行、360、美團等等。

Vue 已是一名前端工程師必備的技能,現在就讓我們開始深入學習 Vue.js 內部的核心技術原理吧!


響應式原理

在前端開發中,"響應式"通常指的是用戶界面對數據的變化做出相應的能力。換句話說,當數據發生變化時,界面能夠自動更新以反映這些變化。這種機制可以讓開發者專注于數據和業務邏輯,而不必手動管理界面的更新。

在Vue.js中,響應式是框架的核心特性之一。在Vue 3中,響應式的原理主要依賴于ES6中的Proxy對象。

具體來說,Vue 3的響應式原理包括以下幾個步驟:

  1. 初始化階段:當你創建一個Vue實例或者定義一個響應式對象時,Vue會對數據進行初始化。在初始化階段,Vue會使用Proxy對象來監聽數據的變化。

  2. Getter和Setter:對象被Proxy包裹后,每個屬性都會有對應的Getter和Setter函數。當你訪問響應式對象的屬性時,會觸發Getter函數,Vue會將這個屬性與當前的組件實例關聯起來,這樣Vue就知道哪些組件依賴于這個屬性。當屬性被修改時,會觸發Setter函數,Vue會通知所有依賴于該屬性的組件進行更新。

  3. 依賴追蹤:Vue使用依賴追蹤來跟蹤數據屬性與組件之間的關聯關系。每個組件都有一個依賴收集器,用于存儲與該組件相關的所有數據屬性。當屬性被訪問時,Vue會將當前組件與這個屬性建立關聯,并將屬性的變化依賴于這個組件。

  4. 觸發更新:當響應式對象的屬性被修改時,會觸發Setter函數。Setter函數會通知所有依賴于這個屬性的組件進行更新,從而使界面能夠反映數據的變化。

總的來說,Vue 3的響應式原理利用了ES6中的Proxy對象來實現數據的監聽和依賴追蹤,從而實現了高效的數據響應式更新。這種機制讓Vue能夠在數據發生變化時自動更新相關的界面組件,使開發者能夠更加專注于業務邏輯的實現。


實現reactive

開發思想,從單元測試出發,先定義自己想要的最終結果,然后逐步實現相關的API

第一步:這里呢,我們定義第一個單元測試

// reactive.spec.ts (這里用的單元測試為 jest)// 這里引入的是我們即將實現的自己的reactive
import { reactive } from "../reactive";
//?定義單元測試的標題為reactive,此處定義為hello world都可以
describe("reactive",()→{it("first case",()→{//?定義一個原生對象const original = {foo:1};//?此處用reactive包裹后返回一個對象const observed = reactive(original);// 期待observed的值不等于originalexpect(observed).not.toBe(original);//?期待observed.foo 為 1expect(observed.foo).toBe(1);});
});

根據上面測試的內容,我們可以實現這樣一個reactive

// reactive.tsexport function reactive(raw) {// reactive 實際上返回的就是一個proxy對象return new Proxy(raw, {// 攔截getget(target, key) {const?res?=?Reflect.get(target,?key);return res;}
}

此時我們已經實現了一個簡易的reactive,只不過還不支持依賴收集和觸發依賴的邏輯。通過上文我們知道,vue3中依賴收集和觸發依賴是在getter和setter中觸發的,所以我們的代碼可以寫成下面這樣:

// reactive.tsexport function reactive(raw) {return new Proxy(raw, {get(target, key) {const res = Reflect.get(target, key);// TODO 依賴收集track(target, key);return res;},set(target,key,value) {const res = Reflect.set(target, key, value);// TODO 觸發依賴trigger(target, key)return res;}
}

此時我們只需要實現 track和trigger即可

下面我們看我們的第二個單元測試:

// effect.spec.tsimport?{?reactive?}?from?'../reactive'
// 這里的effect也是我們后面將要實現的
import { effect } from '../effect'//?effect?就是我們的依賴,也叫做副作用
describe("effect",()→{it("second case",()→{const user = reactive({age: 10,});let nextAge;effect(()→{nextAge=user.age + 1;});expect(nextAge).toBe(11);// updateuser.age++;expect(nextAge).toBe(12);});
});

可以看到上面的單元測試中定義了一個函數effect,effect 是一個函數,用于創建副作用。它是 Vue 3 中響應式 API 的一部分,用于處理響應式數據的變化。effect 函數接受一個回調函數作為參數,并在這個回調函數中定義副作用。當回調函數中依賴的響應式數據發生變化時,副作用將被重新執行

這里先簡單說一下這個依賴收集和觸發依賴是個怎么回事,可以假設這么一個場景:

1. 在火車站都有寄存包裹的地方,每個旅游團就是一個對象,旅游團的每個人就是對象的鍵。

2. 當人員去存儲包裹的時候,寄存處會看當前人員屬于哪個旅游團,相同的旅游團集中放到一個包裹柜,后續方便查找。

3. 然后在這個包裹柜上面找一個箱子給人員,并且給他一把箱子鑰匙(依賴收集

4. 當相同的人員第二次存儲包裹的時候,他會繼續在原有的箱子里放新的東西(依賴收集

5. 以此類推

6.?當人員回來拿包裹時,會把鑰匙給寄存處,寄存處會將鑰匙對應的箱子里的所有東西拿出來(觸發依賴

下面我們來實現effect:

// effect.tsclass ReactiveEffect {private _fn: any;constructor(fn) {this._fn=fn;}run(){activeEffect = thisthis._fn();     }
}// 所有依賴收集到的地方,可以理解成一個寄存處
const targetMap = new Map();
// 收集依賴
export?function?track(target,?key)?{let depsMap = targetMap.get(target);//?先看寄存處里面是否已經由當前對象對應的包裹柜if(!depsMap){depsMap = new Map();targetMap.set(target,depsMap);}let dep = depsMap.get(key)//?再看當前對象對應的鍵值,是否有對應的箱子if(!dep){dep = new Set();depsMap.set(key, dep)}//?最后將用戶傳入的fn作為依賴,添加進入箱子中trackEffects(dep)
}export function trackEffects(dep){dep.add(activeEffect);
}//?實現trigger
export function trigger(target, key) {//?先根據旅游團找到對應的包裹柜let depsMap = targetMap.get(target);//?根據人員找到對應的箱子let dep = depsMap.get(key);//?把箱子里所有的內容拿出來執行triggerEffects(dep)
}export function triggerEffects(dep){for(const effect of dep){effect.run();}
}let activeEffect;
export function effect(fn) {// fnconst _effect = new ReactiveEffect(fn)//?立即執行傳入的函數_effect.run();
}

此時我們的reactive就實現完成了,這里做個總結:

  1. 就是每個鍵在getter的時候,也就是effect函數傳入的時候(這里會觸發getter),將整個effect函數作為依賴,放入鍵值對應的箱子里

  2. 當數據更新的時候,也就是觸發setter時,將箱子里的內容(fn函數)拿出來執行一遍。此時,相關的響應式數據也就更新了


實現ref

有了上面reactive的基礎,ref會相當簡單的學會。我們還是通過一個單元測試開始:

// ref.spec.tsdescribe("ref",()→{it("first?case",()={const a = ref(1);expect(a.value).toBe(1);});it("second case",()=>{            const a = ref(1);let dummy;let calls = 0;effect(()=>{calls++;dummy = a.value;}};expect(calls).toBe(1);expect(dummy).toBe(1);a.value = 2;expect(calls).toBe(2);expect(dummy).toBe(2);})
})

ref都是通過.value來觸發,我們可以使用一個類,然后攔截他的get和set,這里給出最終代碼:

// ref.tsclass RefImpl {private _value: any;//?存放依賴的箱子public dep;constructor(value) {this._value = value;this.dep = new Set();}get value(){//?收集依賴trackEffects(this.dep)return this._value;}set value(newValue){this.value = newValue// 觸發依賴triggerEffects(this.dep)}
}
export function ref(value) {return new RefImpl(value);
}

Vue?進階系列教程將在本號持續發布,一起查漏補缺學個痛快!若您有遇到其它相關問題,非常歡迎在評論中留言討論,達到幫助更多人的目的。若感本文對您有所幫助請點個贊吧!

13fe43985a61ca71b4dca7984f403cd6.png

葉陽輝

HFun?前端攻城獅

往期精彩:

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:
http://www.pswp.cn/news/696521.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/696521.shtml
英文地址,請注明出處:http://en.pswp.cn/news/696521.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

計算機網絡Day02--物理層(一)

計算機網絡Day02–物理層 物理層基本概念 物理層考慮的是怎么才能在連接各種計算機的傳輸媒體上傳輸比特流,而不是具體的傳輸媒體 作用:盡可能屏蔽掉不同傳輸媒體和通信手段的差異 用于物流層的協議也稱為物流層規程 主要作用:解決計算機…

COCI2015-2016#1 RELATIVNOST

P6533 [COCI2015-2016#1] RELATIVNOST 題目大意 小 L L L在賣畫。這些畫分為彩色畫和黑白畫,小 L L L希望有至少 c c c個人會買走他至少一張彩色畫。 第 i i i個人至多會購買 a i a_i ai?張彩色畫或者 b i b_i bi?張黑白畫,且每個人至少購買一張畫。…

Android---Jetpack Compose學習007

Compose 附帶效應 a. 純函數 純函數指的是函數與外界交換數據只能通過函數參數和函數返回值來進行,純函數的運行不會對外界環境產生任何的影響。比如下面這個函數: fun Add(a : Int, b : Int) : Int {return a b } “副作用”(side effe…

單例模式的介紹

單例模式(Singleton)是一種創建型設計模式,它確保一個類只有一個實例,并提供全局訪問點。其核心思想是通過限制類的實例化次數,防止多個實例同時存在,從而避免了多線程競爭和資源浪費,提高了代碼…

【藍橋杯單片機入門記錄】靜態數碼管

目錄 一、數碼管概述 (1)認識數碼管 (2)數碼管的工作原理 (3)LED數碼管驅動方式-靜態顯示 二、數碼管電路圖 三、靜態數碼管顯示例程 (1)例程1:數碼管顯示某一位&a…

vue、thinkphp實現騰訊云對象存儲COS圖片上傳

環境&#xff1a; thinkphp6 vue2 vant2.12 composer安裝qcloud-sts-sdk composer require qcloud_sts/qcloud-sts-sdk獲取COS臨時id、key的sts接口 <?php declare (strict_types 1);namespace app\index\controller; use QCloud\COSSTS\Sts;class CosController {//h…

如何為PostgreSQL設置自增主鍵?

在 PostgreSQL 中&#xff0c;自增主鍵通常是通過使用 SERIAL 類型或在新版本中使用 IDENTITY 列來實現的。 1. 使用 SERIAL 類型 SERIAL 是一個自動增加的整數&#xff0c;常用于主鍵。當插入新的行時&#xff0c;PostgreSQL 會自動為這個列生成一個新的值。 ??例如 CREAT…

PYQT5-自定義事件

from PyQt5.QtCore import QEvent, QObject from PyQt5.QtWidgets import QApplication import sys# 自定義事件類 class CustomEvent(QEvent):# PYQT5 預留給用戶自定義事件類型的起點為 QEvent.User1000custom_event_type QEvent.registerEventType()# 也可以這樣寫# custom…

2024.2.22

P1162 #include<map> #include<vector> #include<iostream> #include<math.h> #include<algorithm> #include<string> using namespace std; const int N 1020; int n; int g[N][N];//標記數組 int a[N][N];//儲存數組 int dx[] { -1…

webstorm光標變成方塊解決辦法_webstorm光標變粗不能換行

webstorms光標變了 鍵盤上的insert是切換的快捷鍵&#xff0c;敲insert就可以來回切換了

回顧 | Java面向對象 多態篇

多態是面向對象編程中的一個重要概念&#xff0c;它允許不同的對象對同一消息做出不同的響應。 通過多態&#xff0c;可以通過父類或接口定義的引用變量來操作子類或實現類的對象&#xff0c;從而實現同一方法在不同對象上的不同行為。 在Java中&#xff0c;多態性主要通過繼…

雙通道并行網絡,想用哪個網絡用哪個,MATLAB代碼

本期可謂是寶藏篇&#xff01;學會本期的思想&#xff0c;幫助你分分鐘找到創新點&#xff0c;且不與別人重復&#xff01; 本期采用MATLAB代碼&#xff0c;實現一種“基于格拉姆角場與并行CNN的故障診斷方法”。該方法的具體實現可以參考文獻&#xff1a; [1]李宗源,陳謙,錢…

React native更改包名后,啟動app的activity包名不生效問題

這篇文章本不算記錄的&#xff0c;因為實際開發中&#xff0c;類似這種小問題會有很多很多&#xff0c;因為導致問題的原因千奇百怪&#xff0c;解決方案也不盡相同&#xff0c;所以也都沒有記錄。 但今天看到我10年寫的問題解決小文章&#xff0c;被網友收藏了&#xff0c; 感…

普中51單片機學習(EEPROM)

EEPROM IIC串行總線的組成及工作原理 I2C總線的數據傳送 數據位的有效性規定 I2C總線進行數據傳送時&#xff0c;時鐘信號為高電平期間&#xff0c;數據線上的數據必須保持穩定&#xff0c;只有在時鐘線上的信號為低電平期間&#xff0c;數據線上的高電平或低電平狀態才允許…

分享WebGL物體三維建模

界面效果 代碼結構 模型素材類似CT (Computed Tomography)&#xff0c;即電子計算機斷層掃描&#xff0c;它是利用精確準直的X線束、γ射線、超聲波等&#xff0c;與靈敏度極高的探測器一同圍繞物體的某一部位作一個接一個的斷面掃描。 坐標系統 渲染流程 渲染流程是個將之前準…

Sora:OpenAI引領AI視頻新時代

Sora - 探索AI視頻模型的無限可能 隨著人工智能技術的飛速發展&#xff0c;AI視頻模型已成為科技領域的新熱點。而在這個浪潮中&#xff0c;OpenAI推出的首個AI視頻模型Sora&#xff0c;以其卓越的性能和前瞻性的技術&#xff0c;引領著AI視頻領域的創新發展。讓我們將一起探討…

C++(12) 模板類、模板繼承(嚴格模式和自由模式)

文章目錄 模版類1. 模版類2. 模版參數限制3. 模版繼承3.1 嚴格模式3.2 自由模式 4. 模版類的模版函數5. 返回值類型帶有模版 模版類 1. 模版類 #include <iostream>using namespace std;/* 當前 Person 類型&#xff0c;聲明了連個模版分別對應NameType 模版類型&#…

C++ array容器用法詳解

array 容器是 C++ 11 標準中新增的序列容器,簡單地理解,它就是在 C++ 普通數組的基礎上,添加了一些成員函數和全局函數。在使用上,它比普通數組更安全(原因后續會講),且效率并沒有因此變差。 和其它容器不同,array 容器的大小是固定的,無法動態的擴展或收縮,這也就意…

【SpringCloud】使用 Spring Cloud Alibaba 之 Sentinel 實現微服務的限流、降級、熔斷

目錄 一、Sentinel 介紹1.1 什么是 Sentinel1.2 Sentinel 特性1.3 限流、降級與熔斷的區別 二、實戰演示2.1 下載啟動 Sentinel 控制臺2.2 后端微服務接入 Sentinel 控制臺2.2.1 引入 Sentinel 依賴2.2.2 添加 Sentinel 連接配置 2.3 使用 Sentinel 進行流控&#xff08;含限流…

SLAM ORB-SLAM2(19)特征點三角化

SLAM ORB-SLAM2(19)特征點三角化 1. 前言2. 初始化參數3. 計算投影矩陣4. 恢復三維點4.1. 計算推導4.2. Triangulate5. 檢查三維點5.1. 檢查三維點的深度值和視差角5.2. 檢查空間點的重投影誤差6. 最后處理1. 前言 在 《SLAM ORB-SLAM2(12)估算運動并初始地圖點》 中了解到…