computed
- 會基於其內部的 響應式依賴 進行快取。
- 只在相關 響應式依賴發生改變 時 它們才會重新求值。
- 可以在將模板中使用的常量放在計算屬性中。
watch
- 監聽資料變化,並在監聽回撥函式中返回資料變更前後的兩個值。
- 用於在資料變化後執行 非同步操作 或者開銷較大的操作。
watchEffect
在 composition API中 watchEffect會在它所依賴的資料發生改變時立即執行,並且執行結果會返回一個函式,我們稱它為stop函式
,可以用於停止監聽資料變化,下面是示例程式碼演示:
const count = ref(0)
// -> log 0
const stop = watchEffect(() => {
console.log(count.value)
})
setTimeout(()=>{
// -> log 1
count.value++
},100)
// -> later
stop()
下面我們來實現以上介紹的幾個composition API
- computed -> let x = computed(()=> count.value + 3);
- watch -> watch(()=> count.value, (curVal, preVal) => {}, { deep, immediate })
- watchEffect -> let stop = watchEffect(()=> count.value + 3)
computed
核心思路是
// 簡單定義
let computed = (fn) => {
let value;
return {
get value() {
return value
}
}
}
// 呼叫
let computedValue = computed(() => count.value + 3)
// 監聽
watchEffect(() => {
document.getElementById('computed').innerText = computedValue.value
});
下面我們在此基礎之上實現依賴更新的操作
let computed = (fn) => {
let value;
return {
get value() {
// 5手動執行一次依賴
value = fn()
return value
}
}
}
let count = ref(1);
let computedValue = computed(() => count.value + 3)
function add() {
document.getElementById('add').addEventListener('click',()=>{
count.value++
})
}
add()
watchEffect(() => {
document.getElementById('text').innerText = count.value
document.getElementById('computed').innerText = computedValue.value
});
依賴快取計算
呈上頁面 -html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue3 - computed</title>
</head>
<body>
<div id="app">
result:
<span id="text">0</span>
<br />
computed:
<span id="computed">0</span>
</div>
<button id="add">add</button>
</body>
</html>
包含了computed的實現的完整js程式碼。
;(function () {
let active
/*
* @params fn -> 要執行的函式
* @params option -> 可選引數
* @return effect -> 執行watchEffect
*/
let effect = (fn, options = {}) => {
let effect = (...args) => {
try {
active = effect
// 避免了死迴圈
return fn(...args)
} finally {
active = null
}
}
// 更新資料時也需要讓schedular執行
effect.options = options
return effect
}
let watchEffect = function (cb) {
let runner = effect(cb)
runner()
}
// 需要有個佇列來儲存各項任務
let queue = []
// 通過微任務方式去執行佇列中的任務
let nextTick = (cb) => Promise.resolve().then(cb)
// 將任務新增到佇列
let queueJob = (job) => {
if (!queue.includes(job)) {
queue.push(job)
nextTick(flushJobs)
}
}
// 執行佇列中的任務
let flushJobs = () => {
let job
while ((job = queue.shift()) !== undefined) {
job()
}
}
// 收集更多依賴
class Dep {
// 依賴收集,將響應依賴新增到deps中
constructor() {
this.deps = new Set()
}
depend() {
if (active) {
this.deps.add(active)
}
}
// 通知所有依賴更新
notify() {
// 將任務加到佇列中
this.deps.forEach((dep) => {
dep.options && dep.options.schedular && dep.options.schedular()
queueJob(dep)
})
}
}
let ref = (initValue) => {
let value = initValue
let dep = new Dep()
return Object.defineProperty({}, 'value', {
get() {
dep.depend()
return value
},
set(newValue) {
value = newValue
dep.notify()
}
})
}
let computed = (fn) => {
let value
let dirty = true
let runner = effect(fn, {
// 通過鉤子函式處理dirty引數
schedular: () => {
if (!dirty) {
dirty = true
}
}
})
return {
get value() {
if (dirty) {
value = runner()
// 快取標識
dirty = false
// 這裡在dirty改變為false之後需要在依賴發生變化時候重置為true,
}
return value
}
}
}
let count = ref(1)
// 同93 資料發生更新時讓dirty 重置
let computedValue = computed(() => count.value + 3)
function add() {
document.getElementById('add').addEventListener('click', () => {
count.value++
})
}
add()
watchEffect(() => {
document.getElementById('text').innerText = count.value
document.getElementById('computed').innerText = computedValue.value
})
})()
watch
// watch(()=> count.value, (curVal, preVal) => {}, { deep, immediate })
;(function () {
let active
/*
* @params fn -> 要執行的函式
* @params option -> 可選引數
* @return effect -> 執行watchEffect
*/
let effect = (fn, options = {}) => {
let effect = (...args) => {
try {
active = effect
// 避免了死迴圈
return fn(...args)
} finally {
active = null
}
}
// 更新資料時也需要讓schedular執行
effect.options = options
return effect
}
let watchEffect = function (cb) {
let runner = effect(cb)
runner()
}
// 需要有個佇列來儲存各項任務
let queue = []
// 通過微任務方式去執行佇列中的任務
let nextTick = (cb) => Promise.resolve().then(cb)
// 將任務新增到佇列
let queueJob = (job) => {
if (!queue.includes(job)) {
queue.push(job)
nextTick(flushJobs)
}
}
// 執行佇列中的任務
let flushJobs = () => {
let job
while ((job = queue.shift()) !== undefined) {
job()
}
}
// 收集更多依賴
class Dep {
// 依賴收集,將響應依賴新增到deps中
constructor() {
this.deps = new Set()
}
depend() {
if (active) {
this.deps.add(active)
}
}
// 通知所有依賴更新
notify() {
// 將任務加到佇列中
this.deps.forEach((dep) => {
dep.options && dep.options.schedular && dep.options.schedular()
queueJob(dep)
})
}
}
let ref = (initValue) => {
let value = initValue
let dep = new Dep()
return Object.defineProperty({}, 'value', {
get() {
dep.depend()
return value
},
set(newValue) {
value = newValue
dep.notify()
}
})
}
let watch = (source, cb, options = {}) => {
const { immediate } = options
const getter = () => {
return source()
}
let oldValue
const runner = effect(getter, {
schedular: () => applyCbk()
})
const applyCbk = () => {
let newValue = runner()
if (newValue !== oldValue) {
cb(newValue, oldValue)
oldValue = newValue
}
}
// 有預設值時執行回撥
if (immediate) {
applyCbk()
} else {
oldValue = runner()
}
}
let count = ref(1)
function add() {
document.getElementById('add').addEventListener('click', () => {
count.value++
})
}
add()
watch(
() => count.value,
(newValue, oldValue) => {
console.log(newValue, oldValue)
},
{ immediate: true }
)
})()
引數1響應式更新,引數2使用schedular執行回撥,引數3 如果存在時就預設執行回撥2
watchEffect
- stop方法的實現
- 陣列API響應式執行依賴更新
- Vue.set的實現,陣列索引加入代理中
// let stop = watchEffect(()=> count.value + 3)
;(function () {
let active
/*
* @params fn -> 要執行的函式
* @params option -> 可選引數
* @return effect -> 執行watchEffect
*/
let effect = (fn, options = {}) => {
// 包裹一次effect 避免對fn的汙染,保證fn純淨
let effect = (...args) => {
try {
active = effect
// 避免了死迴圈
return fn(...args)
} finally {
active = null
}
}
// 更新資料時也需要讓schedular執行
effect.options = options
// 用於反向查詢
effect.deps = [];
return effect
}
let cleanUpEffect = (effect) => {
const { deps } = effect;
deps.forEach(dep => dep.delete(effect))
}
let watchEffect = function (cb) {
let runner = effect(cb)
runner()
// 返回一個stop函式,清楚當前的監聽
return () => {
cleanUpEffect(runner)
}
}
// 需要有個佇列來儲存各項任務
let queue = []
// 通過微任務方式去執行佇列中的任務
let nextTick = (cb) => Promise.resolve().then(cb)
// 將任務新增到佇列
let queueJob = (job) => {
if (!queue.includes(job)) {
queue.push(job)
nextTick(flushJobs)
}
}
// 執行佇列中的任務
let flushJobs = () => {
let job
while ((job = queue.shift()) !== undefined) {
job()
}
}
// 收集更多依賴
class Dep {
// 依賴收集,將響應依賴新增到deps中
constructor() {
this.deps = new Set()
}
depend() {
if (active) {
this.deps.add(active)
// 新增依賴時追加當前的deps, 實現雙向互通。雙向索引
active.deps.push(this.deps)
}
}
// 通知所有依賴更新
notify() {
// 將任務加到佇列中
this.deps.forEach((dep) => {
dep.options && dep.options.schedular && dep.options.schedular()
queueJob(dep)
})
}
}
let ref = (initValue) => {
let value = initValue
let dep = new Dep()
return Object.defineProperty({}, 'value', {
get() {
dep.depend()
return value
},
set(newValue) {
value = newValue
dep.notify()
}
})
}
let count = ref(1)
function add() {
document.getElementById('add').addEventListener('click', () => {
count.value++
})
}
add()
let stop = watchEffect(() => {
document.getElementById('text').innerText = count.value
})
setTimeout(() => {
stop();
}, 3000);
})()
免責宣告
本文是通過對vue響應式computed計算屬性,watch, watchEffect原始碼學習的一些筆記分享,會涉及到一些引用,出處不詳,如商業用途謹慎轉載。