稍微學(xué)一下Vue的數(shù)據(jù)響應(yīng)式(Vue2及Vue3區(qū)別)
什么是數(shù)據(jù)響應(yīng)式
從一開始使用 Vue 時,對于之前的 jq 開發(fā)而言,一個很大的區(qū)別就是基本不用手動操作 dom,data 中聲明的數(shù)據(jù)狀態(tài)改變后會自動重新渲染相關(guān)的 dom。
換句話說就是 Vue 自己知道哪個數(shù)據(jù)狀態(tài)發(fā)生了變化及哪里有用到這個數(shù)據(jù)需要隨之修改。
因此實現(xiàn)數(shù)據(jù)響應(yīng)式有兩個重點問題:
- 如何知道數(shù)據(jù)發(fā)生了變化?
- 如何知道數(shù)據(jù)變化后哪里需要修改?
對于第一個問題,如何知道數(shù)據(jù)發(fā)生了變化,Vue3 之前使用了 ES5 的一個 API Object.defineProperty Vue3 中使用了 ES6 的 Proxy,都是對需要偵測的數(shù)據(jù)進行 變化偵測 ,添加 getter 和 setter ,這樣就可以知道數(shù)據(jù)何時被讀取和修改。
第二個問題,如何知道數(shù)據(jù)變化后哪里需要修改,Vue 對于每個數(shù)據(jù)都收集了與之相關(guān)的 依賴 ,這里的依賴其實就是一個對象,保存有該數(shù)據(jù)的舊值及數(shù)據(jù)變化后需要執(zhí)行的函數(shù)。每個響應(yīng)式的數(shù)據(jù)變化時會遍歷通知其對應(yīng)的每個依賴,依賴收到通知后會判斷一下新舊值有沒有發(fā)生變化,如果變化則執(zhí)行回調(diào)函數(shù)響應(yīng)數(shù)據(jù)變化(比如修改 dom)。
下面詳細分別介紹 Vue2 及 Vue3 的數(shù)據(jù)變化偵測及依賴收集。
Vue2
變化偵測
Object 的變化偵測
轉(zhuǎn)化響應(yīng)式數(shù)據(jù)需要將 Vue 實例上 data 屬性中定義的數(shù)據(jù)通過遞歸將所有屬性都轉(zhuǎn)化為 getter/setter 的形式,Vue 中定義了一個 Observer 類來做這個事情。
function def(obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
class Observer {
constructor(value) {
this.value = value;
def(value, '__ob__', this);
if (!Array.isArray(value)) {
this.walk(value);
}
}
walk(obj) {
for (const [key, value] of Object.entries(obj)) {
defineReactive(obj, key, value);
}
}
}
直接將一個對象傳入 new Observer() 后就對每項屬性都調(diào)用 defineReactive 函數(shù)添加變化偵測,下面定義這個函數(shù):
function defineReactive(data, key, val) {
let childOb = observe(val);
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
// 讀取 data[key] 時觸發(fā)
console.log('getter', val);
return val;
},
set: function (newVal) {
// 修改 data[key] 時觸發(fā)
console.log('setter', newVal);
if (val === newVal) {
return;
}
val = newVal;
}
})
}
function observe(value, asRootData) {
if (typeof val !== 'object') {
return;
}
let ob;
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else {
ob = new Observer(val);
}
return ob;
}
函數(shù)中判斷如果是對象則遞歸調(diào)用 Observer 來實現(xiàn)所有屬性的變化偵測,根據(jù) __ob__ 屬性判斷是否已處理過,防止多次重復(fù)處理,Observer 處理過后會給數(shù)據(jù)添加這個屬性,下面寫一個對象試一下:
const people = {
name: 'c',
age: 12,
parents: {
dad: 'a',
mom: 'b'
},
mates: ['d', 'e']
};
new Observer(people);
people.name; // getter c
people.age++; // getter 12 setter 13
people.parents.dad; // getter {} getter a
打印 people 可以看到所有屬性添加了 getter/setter 方法,讀取 name 屬性時打印了 people.age++ 修改 age 時打印了 getter 12 setter 13 說明 people 的屬性已經(jīng)被全部成功代理監(jiān)聽。
Array 的變化偵測
可以看到前面 Observer 中僅對 Object 類型個數(shù)據(jù)做了處理,為每個屬性添加了 getter/setter,處理后如果屬性值中有數(shù)組,通過 屬性名 + 索引 的方式(如:this.people.mates[0])獲取也是會觸發(fā) getter 的。但是如果通過數(shù)組原型方法修改數(shù)組的值,如 this.people.mates.push('f'),這樣是無法通過 setter 偵測到的,因此,在 Observer 中需要對 Object 和 Array 分別進行單獨的處理。
為偵測到數(shù)組原型方法的操作,Vue 中是通過創(chuàng)建一個攔截器 arrayMethods,并將攔截器重新掛載到數(shù)組的原型對象上。
下面是攔截器的定義:
const ArrayProto = Array.prototype;
const arrayMethods = Object.create(ArrayProto);
;[
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
].forEach(method => {
const original = ArrayProto[method];
Object.defineProperty(arrayMethods, method, {
value: function mutator(...args) {
console.log('mutator:', this, args);
return original.apply(this, args);
},
enumerable: false,
writable: true,
configurable: true
})
})
這里 arrayMethods 繼承了 Array 的原型對象 Array.prototype,并給它添加了 push pop shift unshift splice sort reverse 這些方法,因為數(shù)組是可以通過這些方法進行修改的。添加的 push pop... 方法中重新調(diào)用 original(緩存的數(shù)組原型方法),這樣就不會影響數(shù)組本身的操作。
最后給 Observer 中添加數(shù)組的修改:直接將攔截器掛載到數(shù)組原型對象上
class Observer {
constructor(value) {
this.value = value;
def(value, '__ob__', this);
if (Array.isArray(value)) {
value.__proto__ = arrayMethods;
} else {
this.walk(value);
}
}
walk(obj) {
for (const [key, value] of Object.entries(obj)) {
defineReactive(obj, key, value);
}
}
}
再來驗證一下:
const people = {
name: 'c',
age: 12,
parents: {
dad: 'a',
mom: 'b'
},
mates: ['d', 'e']
};
new Observer(people);
people.mates[0]; // getter (2) ["d", "e"]
people.mates.push('f'); // mutator: (2) ["d", "e"] ["f"]
現(xiàn)在數(shù)組的修改也能被偵測到了。
依賴收集
目前已經(jīng)可以對 Object 及 Array 數(shù)據(jù)的變化進行截獲,那么開始考慮一開始提到的 Vue 響應(yīng)式數(shù)據(jù)的第二個問題:如何知道數(shù)據(jù)變化后哪里需要修改?
最開始已經(jīng)說過,Vue 中每個數(shù)據(jù)都需要收集與之相關(guān)的依賴,用來表示該數(shù)據(jù)變化時需要進行的操作行為。
通過數(shù)據(jù)的變化偵測我們可以知道數(shù)據(jù)何時被讀取或修改,因此可以在數(shù)據(jù)讀取時收集依賴,修改時通知依賴更新,這樣就可以實現(xiàn)數(shù)據(jù)響應(yīng)式了。
依賴收集在哪
為每個數(shù)據(jù)都創(chuàng)建一個收集依賴的對象 dep,對外暴露 depend(收集依賴)、notify(通知依賴更新)的兩個方法,內(nèi)部維護了一個數(shù)組用來保存該數(shù)據(jù)的每項依賴。
對于 Object,可以在 getter 中收集,setter 中通知更新,對 defineReactive 函數(shù)修改如下:
function defineReactive(data, key, val) {
let childOb = observe(val);
// 處理每個響應(yīng)式數(shù)據(jù)時都創(chuàng)建一個對象用來收集依賴
let dep = new Dep();
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
// 收集依賴
dep.depend();
return val;
},
set: function (newVal) {
if (val === newVal) {
return;
}
val = newVal;
// 通知依賴更新
dep.notify();
}
})
}
上面代碼中依賴是收集在一個 Dep 實例對象上的,下面看一下 Dep 這個類。
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
removeSub(sub) {
if (this.subs.length) {
const index = this.subs.indexOf(sub);
this.subs.splice(index, 1);
}
}
depend() {
if (window.target) {
this.addSub(window.target);
}
}
notify() {
const subs = this.subs.slice();
for (let i = 0; i < subs.length; i++) {
subs[i].update();
}
}
}
Dep 的每個實例都有一個保存依賴的數(shù)組 subs,收集依賴時是從全局的一個變量上獲取到并插入 subs,通知依賴時就遍歷所有 subs 成員并調(diào)用其 update 方法。
Object 的依賴收集和觸發(fā)都是在 defineProperty 中進行的,因此 Dep 實例定義在 defineReactive 函數(shù)中就可以讓 getter 和 setter 都拿到。
而對于 Array 來說,依賴可以在 getter 中收集,但觸發(fā)卻是在攔截器中,為了保證 getter 和 攔截器中都能訪問到 Dep 實例,Vue 中給 Observer 實例上添加了 dep 屬性。
class Observer {
constructor(value) {
this.value = value;
this.dep = new Dep();
def(value, '__ob__', this);
if (Array.isArray(value)) {
value.__proto__ = arrayMethods;
} else {
this.walk(value);
}
}
walk(obj) {
for (const [key, value] of Object.entries(obj)) {
defineReactive(obj, key, value);
}
}
}
Observer 在處理數(shù)據(jù)響應(yīng)式時也將自身實例添加到了數(shù)據(jù)的 __ob__ 屬性上,因此在 getter 和攔截器中都能通過響應(yīng)式數(shù)據(jù)本身的 __ob__.dep 拿到其對應(yīng)的依賴。修改 defineReactive 和 攔截器如下:
function defineReactive(data, key, val) {
let childOb = observe(val);
let dep = new Dep();
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
dep.depend();
// 給 Observer 實例上的 dep 屬性收集依賴
if (childOb) {
childOb.dep.depend();
}
return val;
},
...
})
}
;[
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
].forEach(method => {
const original = ArrayProto[method];
def(arrayMethods, method, (...args) => {
const result = original.apply(this, args);
const ob = this.__ob__;
ob.dep.notify();
return result;
})
})
依賴長什么樣
現(xiàn)在已經(jīng)知道了依賴保存在每個響應(yīng)式數(shù)據(jù)對應(yīng)的 Dep 實例中的 subs 中,通過上面 Dep 的代碼可以知道,收集的依賴是一個全局對象,且該對象對外暴露了一個 update 方法,記錄了數(shù)據(jù)變化時需要進行的更新操作(如修改 dom 或 Vue 的 Watch)。
首先這個依賴對象的功能主要有兩點:
- 需要主動將自己收集到對應(yīng)響應(yīng)式數(shù)據(jù)的 Dep 實例中;
- 保存數(shù)據(jù)變化時要進行的操作并在 update 方法中調(diào)用;
其實就是一個中介角色,Vue 中起名為 Watcher。
class Watcher {
constructor(vm, expOrFn, cb) {
this.vm = vm;
// 保存通過表達式獲取數(shù)據(jù)的方法
this.getter = parsePath(expOrFn);
this.cb = cb;
this.value = this.get();
}
get() {
// 將自身 Watcher 實例掛到全局對象上
window.target = this;
// 獲取表達式對應(yīng)的數(shù)據(jù)
// 會自動觸發(fā)該數(shù)據(jù)的 getter
// getter 中收集依賴時從全局對象上拿到這個 Watcher 實例
let value = this.getter.call(this.vm, this.vm);
window.target = undefined;
return value;
}
update() {
const oldValue = this.value;
this.value = this.get();
// 將舊值與新值傳遞給回調(diào)函數(shù)
this.cb.call(this.vm, this.value, oldValue);
}
}
對于第一點,主動將自己收集到 Dep 實例中,Watcher 中設(shè)計的非常巧妙,在 get 中將自身 Watcher 實例掛到全局對象上,然后通過獲取數(shù)據(jù)觸發(fā) getter 來實現(xiàn)依賴收集。
第二點實現(xiàn)很簡單,只需要將構(gòu)造函數(shù)參數(shù)中的回調(diào)函數(shù)保存并在 update 方法中調(diào)用即可。
構(gòu)造函數(shù)中的 parsePath 方法就是從 Vue 實例的 data 上通過表達式獲取數(shù)據(jù),比如表達式為 "user.name" 則需要解析該字符串然后獲取 data.user.name 數(shù)據(jù)。
總結(jié)
- 數(shù)據(jù)先通過調(diào)用 new Observer() 為每項屬性添加變化偵測,并創(chuàng)建一個 Dep 實例用來保存相關(guān)依賴。在讀取屬性值時保存依賴,修改屬性值時通知依賴;
- Dep 實例的 subs 屬性為一個數(shù)組,保存依賴是向數(shù)組中添加,通知依賴時遍歷數(shù)組一次調(diào)用依賴的 update 方法;
- 依賴是一個 Watcher 實例,保存了數(shù)據(jù)變化時需要進行的操作,并將實例自身放到全局的一個位置,然后讀取數(shù)據(jù)觸發(fā)數(shù)據(jù)的 getter,getter 中從全局指定的位置獲取到該 Watcher 實例并收集在 Dep 實例中。

以上就是 Vue2 中的響應(yīng)式原理,在 Observer 處理完后,外界只需要通過創(chuàng)建 Watcher 傳入需要監(jiān)聽的數(shù)據(jù)及數(shù)據(jù)變化時的響應(yīng)回調(diào)函數(shù)即可。
Vue3
Vue3 中每個功能單獨為一個模塊,并可以單獨打包使用,本文僅簡單討論 Vue3 中與數(shù)據(jù)響應(yīng)式相關(guān)的 Reactive 模塊,了解其內(nèi)部原理,與 Vue2 相比又有何不同。
因為該模塊可以單獨使用,先來看一下這個模塊的用法示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue3 demo</title>
</head>
<body>
<div id="app">
<div id="count"></div>
<button id="btn">+1</button>
</div>
<script src="./vue3.js"></script>
<script>
const countEl = document.querySelector('#count')
const btnEl = document.querySelector('#btn')
// 定義響應(yīng)式數(shù)據(jù)
const state = reactive({
count: 0,
man: {
name: 'pan'
}
})
// 定義計算屬性
let double = computed(() => {
return state.count * 2
})
// 回調(diào)函數(shù)立即執(zhí)行一次,內(nèi)部使用到的數(shù)據(jù)更新時會重新執(zhí)行回調(diào)函數(shù)
effect(() => {
countEl.innerHTML = `count is ${state.count}, double is ${double.value}, man's name is ${state.man.name}`
})
// 修改響應(yīng)式數(shù)據(jù)觸發(fā)更新
btnEl.addEventListener('click', () => {
state.count++
}, false)
</script>
</body>
</html>
通過示例可以看到實現(xiàn) Vue3 這個數(shù)據(jù)響應(yīng)式需要有 reactive、computed、effect 這幾個函數(shù),下面仍然通過從變化偵測及依賴收集兩個方面介紹,簡單實現(xiàn)這幾個函數(shù)。
變化偵測
示例中的 reactive 函數(shù)是對數(shù)據(jù)進行響應(yīng)式化的,因此該函數(shù)的功能就類似于 Vue2 中的 defineReactive 函數(shù)的 getter/setter 處理,處理后能夠?qū)?shù)據(jù)的獲取及修改操作進行捕獲。
const toProxy = new WeakMap()
const toRaw = new WeakMap()
const baseHandler = {
get(target, key) {
console.log('Get', target, key)
const res = Reflect.get(target, key)
// 遞歸尋找
return typeof res == 'object' ? reactive(res) : res
},
set(target, key, val) {
console.log('Set', target, key, val)
const res = Reflect.set(target, key, val)
return res
}
}
function reactive(target) {
console.log('reactive', target)
// 查詢緩存
let observed = toProxy.get(target)
if (observed) {
return observed
}
if (toRaw.get(target)) {
return target
}
observed = new Proxy(target, baseHandler)
// 設(shè)置緩存
toProxy.set(target, observed)
toRaw.set(observed, target)
return observed
}
reactive 中使用 Proxy 對目標(biāo)進行代理,代理的行為是 baseHander ,然后對目標(biāo)對象及代理后的對象進行緩存,防止多次代理。
baseHandler 中就是對數(shù)據(jù)的獲取及修改進行攔截,并通過 Reflect 執(zhí)行 get/set 的原本操作,并在獲取值為 Object 時遞歸進行響應(yīng)式處理。很簡單地就完成了數(shù)據(jù)的響應(yīng)式處理。
依賴收集
依賴收集與 Vue2 類似,在 getter 中收集依賴,setter 中觸發(fā)依賴,修改 baseHandler 如下:
const baseHandler = {
get(target, key) {
const res = Reflect.get(target, key)
// 收集依賴
track(target, key)
return typeof res == 'object' ? reactive(res) : res
},
set(target, key, val) {
const info = {
oldValue: target[key],
newValue: val
}
const res = Reflect.set(target, key, val)
// 觸發(fā)更新
trigger(target, key, info)
return res
}
}
track 函數(shù)收集依賴,trigger 函數(shù)觸發(fā)依賴更新。
首先需要兩個全局變量,用于保存當(dāng)前待收集的依賴對象的 effectStack 及一個記錄所有數(shù)據(jù)及其對應(yīng)依賴的表 targetMap 。
const effectStack = [] const targetMap = new WeakMap()
接下來定義這收集依賴及觸發(fā)依賴更新這兩個函數(shù):
function track(target, key) {
// 從棧中拿到待收集的依賴對象
let effect = effectStack[effectStack.length - 1]
if (effect) {
// 通過 target 及 key 從依賴映射表中拿到對應(yīng)的依賴列表(Set類型)
// 首次需要對依賴映射表初始化
let depsMap = targetMap.get(target)
if (depsMap === undefined) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let dep = depsMap.get(key)
if (dep === undefined) {
dep = new Set()
depsMap.set(key, dep)
}
// 若 target.key 對應(yīng)的依賴列表中不存在該依賴則收集
if (!dep.has(effect)) {
dep.add(effect)
}
}
}
function trigger(target, key, info) {
// 依賴映射表中取出 target 相關(guān)數(shù)據(jù)
const depsMap = targetMap.get(target)
if (depsMap === undefined) {
return
}
// 普通依賴對象的列表
const effects = new Set()
// 計算屬性依賴對象的列表
const computedRunners = new Set()
if (key) {
// 取出 key 相關(guān)的依賴列表遍歷分類存入 effects 及 computedRunners
let deps = depsMap.get(key)
deps.forEach(effect => {
if (effect.computed) {
computedRunners.add(effect)
} else {
effects.add(effect)
}
})
}
// 遍歷執(zhí)行所有依賴對象
const run = effect=> effect()
effects.forEach(run)
computedRunners.forEach(run)
}
track 及 trigger 的大致代碼也很簡單,track 是拿到待收集的依賴對象 effect 后收集到 effectStack,trigger 是從 effectStack 拿到對應(yīng)的依賴列表遍歷執(zhí)行。
到現(xiàn)在就差這個依賴對象了,根據(jù)上面 trigger 函數(shù)可以知道,這個依賴 effect 首先是個函數(shù)可以執(zhí)行,并且還有自身屬性,如 computed 表示其為一個計算屬性的依賴,有時會根據(jù)該標(biāo)識進行寫特殊處理。
下面開始介紹這個依賴對象是如何產(chǎn)生的:
// 創(chuàng)建依賴對象
function createReactiveEffect(fn, options) {
const effect = function effect(...args) {
return run(effect, fn, args)
}
effect.computed = options.computed
effect.lazy = options.lazy
return effect
}
function run(effect, fn, args) {
if (!effectStack.includes(effect)) {
try {
effectStack.push(effect)
return fn(...args)
} finally {
effectStack.pop()
}
}
}
createReactiveEffect 是一個高階函數(shù),內(nèi)部創(chuàng)建了一個名為 effect 的函數(shù),函數(shù)內(nèi)部返回的是一個 run 函數(shù),run 函數(shù)中將依賴 effect 對象存入全局的待收集依賴棧 effectStack 中,并執(zhí)行傳入的回調(diào)函數(shù),該回調(diào)函數(shù)其實就是一開始示例中 effect 函數(shù)傳入的修改 Dom 的函數(shù)。也就是說依賴對象作為函數(shù)直接執(zhí)行就會添加依賴到全局棧并執(zhí)行回調(diào)函數(shù)。
回調(diào)函數(shù)中如果有讀取了響應(yīng)式數(shù)據(jù)的話則會觸發(fā) proxy 的 get 收集依賴,這時就能從 effectStack 上拿到該依賴對象了。
然后給 effect 增加了 computed lazy 屬性后返回。
最后就是對外暴露的 effect 及 computed 函數(shù)了:
// 創(chuàng)建依賴對象并判斷非計算屬性則立即執(zhí)行
function effect(fn, options = {}) {
let e = createReactiveEffect(fn, options)
if (!options.lazy) {
e()
}
return e
}
// computed 內(nèi)部調(diào)用 effect 并添加計算屬性相關(guān)的 options
function computed(fn) {
const runner = effect(fn, {
computed: true,
lazy: true
})
return {
effect: runner,
get value() {
return runner()
}
}
}
computed 就不多說了,effect 就是將傳入的回調(diào)函數(shù)傳給 createReactiveEffect 創(chuàng)建依賴對象,然后執(zhí)行依賴對象就會執(zhí)行回調(diào)函數(shù)并收集該依賴對象。
總結(jié)
- reactive 將傳入的數(shù)據(jù)對象使用 proxy 包裝,通過 proxy 的 get set 攔截數(shù)據(jù)的獲取及修改,與 Vue2 的 defineProperty 一樣,在 get 中收集依賴,在 set 中觸發(fā)依賴;
- effect 函數(shù)接受一個回調(diào)函數(shù)作為參數(shù),將回調(diào)函數(shù)包裝一下作為依賴對象后執(zhí)行回調(diào)函數(shù),回調(diào)函數(shù)執(zhí)行時觸發(fā)相關(guān)數(shù)據(jù)的 get 后進行依賴收集;

到此 Vue2 及 Vue3 中的數(shù)據(jù)響應(yīng)式原理都分析完了。
Vue2 及 Vue3 數(shù)據(jù)響應(yīng)式的對比
本次 Vue 對于數(shù)據(jù)響應(yīng)式的升級主要在變化偵測部分。
Vue2 中的變化偵測實現(xiàn)對 Object 及 Array 分別進行了不同的處理,Objcet 使用了
Object.defineProperty API ,Array 使用了攔截器對 Array 原型上的能夠改變數(shù)據(jù)的方法進行攔截。雖然也實現(xiàn)了數(shù)據(jù)的變化偵測,但存在很多局限 ,比如對象新增屬性無法被偵測,以及通過數(shù)組下邊修改數(shù)組內(nèi)容,也因此在 Vue2 中經(jīng)常會使用到 $set 這個方法對數(shù)據(jù)修改,以保證依賴更新。
Vue3 中使用了 es6 的 Proxy API 對數(shù)據(jù)代理,沒有像 Vue2 中對原數(shù)據(jù)進行修改,只是加了代理包裝,因此首先性能上會有所改善。其次解決了 Vue2 中變化偵測的局限性,可以不使用 $set 新增的對象屬性及通過下標(biāo)修改數(shù)組都能被偵測到。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 前端面試之vue2和vue3的區(qū)別有哪些
- vue2.x,vue3.x使用provide/inject注入的區(qū)別說明
- vue3的watch用法以及和vue2中watch的區(qū)別
- Vue2和Vue3在v-for遍歷時ref獲取dom節(jié)點的區(qū)別及說明
- 由淺入深講解vue2和vue3的區(qū)別
- vue2與vue3中生命周期執(zhí)行順序的區(qū)別說明
- vue2與vue3雙向數(shù)據(jù)綁定的區(qū)別說明
- Vue2與Vue3兄弟組件通訊bus的區(qū)別及用法
- Vue2.x與Vue3.x中路由鉤子的區(qū)別詳解
- Vue2.X和Vue3.0數(shù)據(jù)響應(yīng)原理變化的區(qū)別
- 深入淺出分析vue2和vue3的區(qū)別
相關(guān)文章
vue主動刷新頁面及列表數(shù)據(jù)刪除后的刷新實例
今天小編就為大家分享一篇vue主動刷新頁面及列表數(shù)據(jù)刪除后的刷新實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-09-09

