深度解析?Vue3?的響應式機制
什么是響應式
響應式一直都是 Vue 的特色功能之一;與之相比,JavaScript 里面的變量,是沒有響應式這個概念的;你在學習 JavaScript 的時候首先被灌輸?shù)母拍?,就是代碼是自上而下執(zhí)行的;
我們看下面的代碼,代碼在執(zhí)行后,打印輸出的兩次 double 的結果也都是 2;即使 我們修改了代碼中 count 的值后,double 的值也不會有任何改變
let?count?= 1 let?double?= count?* 2 count?= 2
double 的值是根據(jù) count 的值乘以二計算而得到的,如果現(xiàn)在我們想讓 doube 能夠跟著 count 的變化而變化,那么我們就需要在每次 count 的值修改后,重新計算 double
比如,在下面的代碼,我們先把計算 doube 的邏輯封裝成函數(shù),然后在修改完 count 之 后,再執(zhí)行一遍,你就會得到最新的 double 值
let?count?= 1 // 計算過程封裝成函數(shù) let?getDouble?= n=>n*2 //箭頭函數(shù) let?double?= getDouble(count) count?= 2 //?重新計算double?,這里我們不能自動執(zhí)行對double的計算 double?= getDouble(count)
實際開發(fā)中的計算邏輯會比計算 doube 復雜的多,但是都可以封裝成一個函數(shù)去執(zhí)行;下 一步,我們要考慮的是,如何讓 double 的值得到自動計算
如果我們能讓 getDouble 函數(shù)自動執(zhí)行,也就是如下圖所示,我們使用 JavaScript 的某種機制,把 count 包裹一層,每當對 count 進行修改時,就去同步更新 double 的值,那 么就有一種 double 自動跟著 count 的變化而變化的感覺,這就算是響應式的雛形了
響應式原理
響應式原理是什么呢?Vue 中用過三種響應式解決方案,分別是 defineProperty、Proxy 和 value setter我們首先來看 Vue 2 的 defineProperty API
這里我結合一個例子來說明,在下面的代碼中,我們定義個一個對象 obj,使用 defineProperty 代理了 count 屬性;這樣我們就對 obj 對象的 value 屬性實現(xiàn)了攔截,讀取 count 屬性的時候執(zhí)行 get 函數(shù),修改 count 屬性的時候執(zhí)行 set 函數(shù),并在 set 函數(shù)內(nèi)部重新計算了 double
let getDouble = n=>n*2 let obj = {} let count = 1 let double = getDouble(count) Object.defineProperty(obj,'count',{ get(){ return count }, set(val){ count = val double = getDouble(val) } }) console.log(double) // 打印2 obj.count = 2 console.log(double) // 打印4 有種自動變化的感覺
這樣我們就實現(xiàn)了簡易的響應式功能,在課程的第四部分,我還會帶著你寫一個更完善的響應式系統(tǒng)
但 defineProperty API 作為 Vue 2 實現(xiàn)響應式的原理,它的語法中也有一些缺陷;比如在下面代碼中,我們刪除 obj.count 屬性,set 函數(shù)就不會執(zhí)行,double 還是之前的數(shù)值;這也是為什么在 Vue 2 中,我們需要 $delete
一個專門的函數(shù)去刪除數(shù)據(jù)
delete obj.count console.log(double) // doube還是4
Vue 3 的響應式機制是基于 Proxy 實現(xiàn)的;就 Proxy 這個名字來說,你也能看出來這是代理的意思,Proxy 的重要意義在于它解決了 Vue 2 響應式的缺陷
我們看下面的代碼,在其中我們通過 new Proxy 代理了 obj 這個對象,然后通過 get、set 和 deleteProperty 函數(shù)代理了對象的讀取、修改和刪除操作,從而實現(xiàn)了響應式的功能
let proxy = new Proxy(obj,{ get : function (target,prop) { return target[prop] }, set : function (target,prop,value) { target[prop] = value; if(prop==='count'){ double = getDouble(value) } }, deleteProperty(target,prop){ delete target[prop] if(prop==='count'){ double = NaN } } }) console.log(obj.count,double) proxy.count = 2 console.log(obj.count,double) delete proxy.count // 刪除屬性后,我們打印log時,輸出的結果就會是 undefined NaN console.log(obj.count,double)
我們從這里可以看出 Proxy 實現(xiàn)的功能和 Vue 2 的 definePropery 類似,它們都能夠在用戶修改數(shù)據(jù)的時候觸發(fā) set 函數(shù),從而實現(xiàn)自動更新 double 的功能。而且 Proxy 還完善了幾個 definePropery 的缺陷,比如說可以監(jiān)聽到屬性的刪除
Proxy 是針對對象來監(jiān)聽,而不是針對某個具體屬性,所以不僅可以代理那些定義時不存在的屬性,還可以代理更豐富的數(shù)據(jù)結構,比如 Map、Set 等,并且我們也能通過 deleteProperty 實現(xiàn)對刪除操作的代理
當然,為了幫助你理解 Proxy,我們還可以把 double 相關的代碼都寫在 set 和 deleteProperty 函數(shù)里進行實現(xiàn),在課程的后半程我會帶你做好更完善的封裝;比如下面代碼中,Vue 3 的 reactive 函數(shù)可以把一個對象變成響應式數(shù)據(jù),而 reactive 就是基于 Proxy 實現(xiàn)的;我們還可以通過 watchEffect,在 obj.count 修改之后,執(zhí)行數(shù)據(jù)的打印
import {reactive,computed,watchEffect} from 'vue' let obj = reactive({ count:1 }) let double = computed(()=>obj.count*2) obj.count = 2 watchEffect(()=>{ console.log('數(shù)據(jù)被修改了',obj.count,double.value) })
有了 Proxy 后,響應式機制就比較完備了;但是在 Vue 3 中還有另一個響應式實現(xiàn)的邏輯,就是利用對象的 get 和 set 函數(shù)來進行監(jiān)聽,這種響應式的實現(xiàn)方式,只能攔截某一個屬性的修改,這也是 Vue 3 中 ref 這個 API 的實現(xiàn)
在下面的代碼中,我們攔截了 count 的 value 屬性,并且攔截了 set 操作,也能實現(xiàn)類似的功能
let getDouble = n => n * 2 let _value = 1 double = getDouble(_value) let count = { get value() { return _value }, set value(val) { _value = val double = getDouble(_value) } } console.log(count.value,double) count.value = 2 console.log(count.value,double)
三種實現(xiàn)原理的對比表格如下,幫助你理解三種響應式的區(qū)別
實現(xiàn)原理 | defineProperty | Proxy | value setter |
---|---|---|---|
實際場景 | Vue 2 響應式 | Vue 3 reactive | Vue 3 ref |
優(yōu)勢 | 兼容性 | 基于proxy實現(xiàn)真正的攔截 | 實現(xiàn)簡單 |
劣勢 | 數(shù)組和屬性刪除等攔截不了 | 兼容不了 IE11 | 只攔截了 value 屬性 |
實際應用 | Vue 2 | Vue 3 復雜數(shù)據(jù)結構 | Vue 3 簡單數(shù)據(jù)結構 |
定制響應式數(shù)據(jù)
簡單入門響應式的原理后,接下來我們學習一下響應式數(shù)據(jù)在使用的時候的進階方式;我們看下使用 <script setup>
重構之后的 todolist 的代碼;這段代碼使用 watchEffect,數(shù)據(jù)變化之后會把數(shù)據(jù)同步到 localStorage 之上,這樣我們就實現(xiàn)了 todolist 和本地存儲的同步
import { ref, watchEffect, computed } from "vue"; let title = ref(""); let todos = ref(JSON.parse(localStorage.getItem('todos')||'[]')); watchEffect(()=>{ localStorage.setItem('todos',JSON.stringify(todos.value)) }) function addTodo() { todos.value.push({ title: title.value, done: false, }); title.value = ""; }
更進一步,我們可以直接抽離一個 useStorage 函數(shù),在響應式的基礎之上,把任意數(shù)據(jù)響應式的變化同步到本地存儲;我們先看下面的這段代碼,ref 從本地存儲中獲取數(shù)據(jù),封裝成響應式并且返回,watchEffect 中做本地存儲的同步,useStorage 這個函數(shù)可以抽離成一個文件,放在工具函數(shù)文件夾中
function useStorage(name, value=[]){ let data = ref(JSON.parse(localStorage.getItem(name)||'[]')) watchEffect(()=>{ localStorage.setItem(name,JSON.stringify(data.value)) }) return data }
在項目中我們使用下面代碼的寫法,把 ref 變成 useStorage,這也是 Composition API 最大的優(yōu)點,也就是可以任意拆分出獨立的功能
let todos = useStorage('todos',[]) function addTodo() { ...code }
現(xiàn)在,你應該已經(jīng)學會了在 Vue 內(nèi)部進階地使用響應式機制,去封裝獨立的函數(shù);在后續(xù)的實戰(zhàn)應用中,我們也會經(jīng)常對通用功能進行封裝;如下圖所示,我們可以把日常開發(fā)中用到的數(shù)據(jù),無論是瀏覽器的本地存儲,還是網(wǎng)絡數(shù)據(jù),都封裝成響應式數(shù)據(jù),統(tǒng)一使用響應式數(shù)據(jù)開發(fā)的模式;這樣,我們開發(fā)項目的時候,只需要修改對應的數(shù)據(jù)就可以了
基于響應式的開發(fā)模式,我們還可以按照類似的原理,把我們需要修改的數(shù)據(jù),都變成響應式;比如,我們可以在 loading 狀態(tài)下,去修改瀏覽器的小圖標 favicon;和本地存儲類似,修改 favicon 時,我們需要找到 head 中有 icon 屬性的標簽
在下面的代碼中,我們把對圖標的對應修改的操作封裝成了 useFavicon 函數(shù),并且通過 ref 和 watch 的包裹,我們還把小圖標變成了響應式數(shù)據(jù)
import {ref,watch} from 'vue' export default function useFavicon( newIcon ) { const favicon = ref(newIcon) const updateIcon = (icon) => { document.head .querySelectorAll(`link[rel*="icon"]`) .forEach(el => el.href = `${icon}`) } watch( favicon, (i) => { updateIcon(i) } ) return {favicon,reset} }
這樣在組件中,我們就可以通過響應式的方式去修改和使用小圖標,通過對 faivcon.value 的修改就可以隨時更換網(wǎng)站小圖標;下面的代碼,就實現(xiàn)了在點擊按鈕之后,修改了網(wǎng)頁的圖標為 geek.png 的操作
<script setup> import useFavicon from './utils/favicon' let {favicon} = useFavicon() function loading(){ favicon.value = '/geek.png' } </script> <template> <button @click="loading">123</button> </template>
Vueuse 工具包
我們自己封裝的 useStorage,算是把 localStorage 簡單地變成了響應式對象,實現(xiàn)數(shù)據(jù)的更新和localStorage 的同步;同理,我們還可以封裝更多的類似 useStorage 函數(shù)的其他 use 類型的函數(shù),把實際開發(fā)中你用到的任何數(shù)據(jù)或者瀏覽器屬性,都封裝成響應式數(shù)據(jù),這樣就可以極大地提高我們的開發(fā)效率
Vue 社區(qū)中其實已經(jīng)有一個類似的工具集合,也就是 VueUse,它把開發(fā)中常見的屬性都封裝成為響應式函數(shù)
VueUse 趁著這一波 Vue 3 的更新,跟上了響應式 API 的潮流;VueUse 的官方的介紹說這是一個 Composition API 的工具集合,適用于 Vue 2.x 或者 Vue 3.x,用起來和 React Hooks 還挺像的
在項目目錄下打開命令行里,我們輸入如下命令,來進行 VueUse 插件的安裝:
npm install @vueuse/core
然后,我們就先來使用一下 VueUse;在下面這段代碼中,我們使用 useFullscreen 來返回全屏的狀態(tài)和切換全屏的函數(shù);這樣,我們就不需要考慮瀏覽器全屏的 API,而是直接使用 VueUse 響應式數(shù)據(jù)和函數(shù)就可以很輕松地在項目中實現(xiàn)全屏功能
<template> <h1 @click="toggle">click</h1> </template> <script setup> import { useFullscreen } from '@vueuse/core' const { isFullscreen, enter, exit, toggle } = useFullscreen() </script>
useFullscreen 的封裝邏輯和 useStorage 類似,都是屏蔽了瀏覽器的操作,把所有我們需要用到的狀態(tài)和數(shù)據(jù)都用響應式的方式統(tǒng)一管理,VueUse 中包含了很多我們常用的工具函數(shù),我們可以把網(wǎng)絡狀態(tài)、異步請求的數(shù)據(jù)、動畫和事件等功能,都看成是響應式的數(shù)據(jù)去管理
到此這篇關于深度解析 Vue3 的響應式機制的文章就介紹到這了,更多相關Vue3 響應式機制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
element-ui 限制日期選擇的方法(datepicker)
本篇文章主要介紹了element-ui 限制日期選擇的方法(datepicker),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05