響應式一直都是Vue 的特色功能之一;與之相比,JavaScript 裡面的變量,是沒有響應式這個概念的;你在學習JavaScript 的時候首先被灌輸的概念,就是程式碼是自上而下執行的;
我們看下面的程式碼,程式碼執行後,列印輸出的兩次double 的結果也都是2 ;即使我們修改了程式碼中count 的值後,double 的值也不會有任何改變
let count = 1 let double = count * 2 count = 2
double 的值是根據count 的值乘以二計算而得到的,如果現在我們想讓doube能夠跟著count 的變化而變化,那麼我們就需要在每次count 的值修改後,重新計算double
比如,在下面的程式碼,我們先把計算doube 的邏輯封裝成函數,然後在修改完count 之後,再執行一遍,你就會得到最新的double 值
let count = 1 // 计算过程封装成函数 let getDouble = n=>n*2 //箭头函数 let double = getDouble(count) count = 2 // 重新计算double ,这里我们不能自动执行对double的计算 double = getDouble(count)
實際開發中的計算邏輯會比計算doube 複雜的多,但是都可以封裝成一個函數去執行;下一步,我們要考慮的是,如何讓double 的值得到自動計算
如果我們能讓getDouble 函數自動執行,也就是如下圖所示,我們使用JavaScript 的某種機制,把count 包裹一層,每當對count 進行修改時,就去同步更新double 的值,那麼就有一種double 自動跟著count 的變化而變化的感覺,這就算是響應式的雛形了
響應式原理是什麼呢? Vue 中用過三種響應式解決方案,分別是defineProperty、Proxy 和value setter我們先來看Vue 2 的defineProperty API
這裡我結合一個例子來說明,在下面的程式碼中,我們定義個一個物件obj,使用defineProperty 代理了count 屬性;這樣我們就對obj 物件的value 屬性實作了攔截,讀取count 屬性的時候執行get 函數,修改count 屬性的時候執行set 函數,並在set 函數內部重新計算了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 有种自动变化的感觉
這樣我們就實現了簡易的響應式功能,在課程的第四部分,我還會帶著你寫一個更完善的響應式系統
但defineProperty API 作為Vue 2 實作響應式的原理,它的語法中也有一些缺陷;例如在下面程式碼中,我們刪除obj.count 屬性,set 函數就不會執行,double 還是之前的數值;這也是為什麼在Vue 2中,我們需要$delete
一個專門的函數去刪除資料
delete obj.count console.log(double) // doube还是4
Vue 3 的響應式機制是基於Proxy 實現的;就Proxy 這個名字來說,你也能看出來這就是代理的意思,Proxy 的重要意義在於它解決了Vue 2 響應式的缺陷
我們看下面的程式碼,在其中我們透過new Proxy 代理了obj 這個對象,然後透過get、set 和deleteProperty 函數代理了物件的讀取、修改和刪除操作,從而實現了響應式的功能
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 實現的功能和Vue 2 的definePropery 類似,它們都能夠在用戶修改資料的時候觸發set 函數,從而實現自動更新double 的功能。而且Proxy 也完善了幾個definePropery 的缺陷,比如說可以監聽到屬性的刪除
#Proxy 是針對物件來監聽,而不是針對某個具體屬性,所以不僅可以代理那些定義時不存在的屬性,也可以代理更豐富的資料結構,像是Map、Set 等,我們也能透過deleteProperty 實作對刪除操作的代理
當然,為了幫助你理解Proxy,我們也可以把double 相關的程式碼都寫在set 和deleteProperty 函數裡進行實現,在課程的後半程我會帶你做好更完善的封裝;比如下面程式碼中,Vue 3 的reactive 函數可以把一個物件變成響應式數據,而reactive 就是基於Proxy 實現的;我們也可以透過watchEffect,在obj.count 修改之後,執行資料的列印
import {reactive,computed,watchEffect} from 'vue' let obj = reactive({ count:1 }) let double = computed(()=>obj.count*2) obj.count = 2 watchEffect(()=>{ console.log('数据被修改了',obj.count,double.value) })
有了Proxy 後,響應式機制就比較完備了;但是在Vue 3 中還有另一個響應式實現的邏輯,就是利用物件的get 和set 函數來進行監聽,這種響應式的實作方式,只能攔截某一個屬性的修改,這也是Vue 3 中ref 這個API 的實作
在下面的程式碼中,我們攔截了count 的value 屬性,並且攔截了set 操作,也能實現類似的功能
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)
三種實現原理的對比表格如下,幫助你理解三種響應式的差異
实现原理 | defineProperty | Proxy | value setter |
---|---|---|---|
实际场景 | Vue 2 响应式 | Vue 3 reactive | Vue 3 ref |
优势 | 兼容性 | 基于proxy实现真正的拦截 | 实现简单 |
劣势 | 数组和属性删除等拦截不了 | 兼容不了 IE11 | 只拦截了 value 属性 |
实际应用 | Vue 2 | Vue 3 复杂数据结构 | Vue 3 简单数据结构 |
简单入门响应式的原理后,接下来我们学习一下响应式数据在使用的时候的进阶方式;我们看下使用 <script setup></script>
重构之后的 todolist 的代码;这段代码使用 watchEffect,数据变化之后会把数据同步到 localStorage 之上,这样我们就实现了 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 函数,在响应式的基础之上,把任意数据响应式的变化同步到本地存储;我们先看下面的这段代码,ref 从本地存储中获取数据,封装成响应式并且返回,watchEffect 中做本地存储的同步,useStorage 这个函数可以抽离成一个文件,放在工具函数文件夹中
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 最大的优点,也就是可以任意拆分出独立的功能
let todos = useStorage('todos',[]) function addTodo() { ...code }
现在,你应该已经学会了在 Vue 内部进阶地使用响应式机制,去封装独立的函数;在后续的实战应用中,我们也会经常对通用功能进行封装;如下图所示,我们可以把日常开发中用到的数据,无论是浏览器的本地存储,还是网络数据,都封装成响应式数据,统一使用响应式数据开发的模式;这样,我们开发项目的时候,只需要修改对应的数据就可以了
基于响应式的开发模式,我们还可以按照类似的原理,把我们需要修改的数据,都变成响应式;比如,我们可以在 loading 状态下,去修改浏览器的小图标 favicon;和本地存储类似,修改 favicon 时,我们需要找到 head 中有 icon 属性的标签
在下面的代码中,我们把对图标的对应修改的操作封装成了 useFavicon 函数,并且通过 ref 和 watch 的包裹,我们还把小图标变成了响应式数据
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 的修改就可以随时更换网站小图标;下面的代码,就实现了在点击按钮之后,修改了网页的图标为 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>
我们自己封装的 useStorage,算是把 localStorage 简单地变成了响应式对象,实现数据的更新和localStorage 的同步;同理,我们还可以封装更多的类似 useStorage 函数的其他 use 类型的函数,把实际开发中你用到的任何数据或者浏览器属性,都封装成响应式数据,这样就可以极大地提高我们的开发效率
Vue 社区中其实已经有一个类似的工具集合,也就是 VueUse,它把开发中常见的属性都封装成为响应式函数
VueUse 趁着这一波 Vue 3 的更新,跟上了响应式 API 的潮流;VueUse 的官方的介绍说这是一个 Composition API 的工具集合,适用于 Vue 2.x 或者 Vue 3.x,用起来和 React Hooks 还挺像的
在项目目录下打开命令行里,我们输入如下命令,来进行 VueUse 插件的安装:
npm install @vueuse/core
然后,我们就先来使用一下 VueUse;在下面这段代码中,我们使用 useFullscreen 来返回全屏的状态和切换全屏的函数;这样,我们就不需要考虑浏览器全屏的 API,而是直接使用 VueUse 响应式数据和函数就可以很轻松地在项目中实现全屏功能
<template> <h2 @click="toggle">click</h2> </template> <script setup> import { useFullscreen } from '@vueuse/core' const { isFullscreen, enter, exit, toggle } = useFullscreen() </script>
useFullscreen 的封装逻辑和 useStorage 类似,都是屏蔽了浏览器的操作,把所有我们需要用到的状态和数据都用响应式的方式统一管理,VueUse 中包含了很多我们常用的工具函数,我们可以把网络状态、异步请求的数据、动画和事件等功能,都看成是响应式的数据去管理。
以上是Vue3中的響應式機制是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!