Vue3 computed初始化獲取設(shè)置值實(shí)現(xiàn)示例
computed 用法
本文給大家?guī)淼氖莢ue3 中 computed API的實(shí)現(xiàn)。
大家看過vue3的官網(wǎng),應(yīng)該都知道,在vue3 的組合式API中,computed這個(gè)功能與以往的有所不同了。
以往vue2 的 computed 用法:
export default { components: { }, computed: { people() { return '這個(gè)人' + this.age + '歲' } }, watch() { }, data() { return { age: 1 } } }
現(xiàn)在我們大多數(shù)情況使用的是組合式 API,可以直接使用computed函數(shù)來進(jìn)行屬性的監(jiān)聽。
比如官網(wǎng)的這種案例寫法:
const count = ref(1) const plusOne = computed(() => count.value + 1) // 只讀屬性 console.log(plusOne.value) // 2
vue3 中的 computed 接受一個(gè) getter
函數(shù),返回一個(gè)響應(yīng)式的ref
對象,并且該對象是只讀屬性。讀取該屬性通過 .value
的形式來對外暴露該屬性的值。
此外,如果想要修改該對象,可以傳入一個(gè)帶有 get
和 set
函數(shù)的對象,通過get
和 set
分別讀取和設(shè)置值。
我們來看看這樣的一個(gè)簡單的computed用法:
<div id="app"></div> <!-- <script src="./reactivity.global.js"></script> --> <script src="../../../../node_modules/@vue/reactivity/dist//reactivity.global.js"></script> <script> const { reactive, effect, computed } = VueReactivity const author = reactive({ name: 'clying', sex: '女' }) // 用法一: // const introduction = computed({ // 調(diào)用的是defineProperty 中的 // // getter // get() { // console.log('run get'); // return author.name + '是個(gè)' + author.sex + '程序員!' // }, // // setter // set(newValue) { // // console.log(newValue); // // 注意:我們這里使用的是解構(gòu)賦值語法 // [author.name, author.sex] = newValue.split(' ') // } // }) // 用法二: // 一個(gè)計(jì)算屬性 ref const introduction = computed(() => { console.log('run get'); return author.name + '是個(gè)' + author.sex + '程序員!' }) effect(() => { app.innerHTML = introduction.value // computed通過 .value 讀取 }) introduction.value introduction.value introduction.value </script>
在上述案例中,我們可以很容易就知道,不管讀取introduction
幾次,只要值未發(fā)生改變,始終只會輸出一次run get
。
computed 實(shí)現(xiàn)
那么既然使用vue可以實(shí)現(xiàn),是不是我們也可以自己去搞一個(gè)叻?
其實(shí),computed 也是基于effect這個(gè)功能函數(shù)來實(shí)現(xiàn)的。我們可以在我們完成的effect功能上繼續(xù)擴(kuò)展。
computed 初始化
一步步來,我們先來實(shí)現(xiàn)computed的讀取值功能。
const introduction = computed({ // 調(diào)用的是defineProperty 中的 // getter get() { console.log('run get'); return author.name + '是個(gè)' + author.sex + '程序員!' }, // setter set(newValue) { console.log(newValue); // 注意:我們這里使用的是解構(gòu)賦值語法 [author.name, author.sex] = newValue.split(' ') } })
使用原有的 computed API可以發(fā)現(xiàn),計(jì)算屬性introduction
其實(shí)是一個(gè)ComputedRefImpl
類。
那么我們要做的就是先初始化一個(gè)類,然后對外暴露其實(shí)例。computed可以接收兩種寫法,那么我們在接收參數(shù)的時(shí)候,需要判斷下用戶傳入的是對象還是一個(gè)回調(diào)函數(shù)。
- 如果接收的是一個(gè)回調(diào)函數(shù),那么就只存在取值的
get
,無法設(shè)置值; - 如果接收的是一個(gè)對象,那么就應(yīng)該存在取值的
get
和設(shè)置值的set
。
export const computed = (getterOrOptions) => { let onlyGetter = isFunction(getterOrOptions) // 用戶傳入的回調(diào) let getter let setter if (onlyGetter) { getter = getterOrOptions setter = () => { console.warn('no set'); } } else { // 用戶傳入的包含get set函數(shù)的對象 getter = getterOrOptions.get setter = getterOrOptions.set } // ref 引用類型 return new ComputedRefImpl(getter, setter) }
對外暴露的是ComputedRefImpl
實(shí)例,那么我們初始化還需要在創(chuàng)建一個(gè)ComputedRefImpl
的類。ComputedRefImpl
類中應(yīng)該是一個(gè)effect,并且應(yīng)該可以設(shè)置個(gè)讀取相關(guān)的屬性。在屬性訪問器get和set中,它們操作的需要是同一個(gè)值,所以我們還需要兩個(gè)屬性訪問器操作的同一個(gè)值_value
,且是私有屬性。
ps:類中的屬性訪問器get和set,底層調(diào)用的是Object.defineProperty
。
class ComputedRefImpl { public readonly effect private _value// get和set需要使用的同一個(gè)值 constructor(getter, private readonly _setter) { } get value() { return this._value } set value(newValue) { this._setter(newValue) } }
computed 獲取值的實(shí)現(xiàn)
值的展示
在我們完成初始化之后,頁面上其實(shí)是不會存在我們期望的內(nèi)容的。它其實(shí)是這樣的一個(gè)頁面:
必然。我們還沒有將用戶傳入的邏輯進(jìn)行處理,所以根本看不到呀!
那我們現(xiàn)在要做的就是先將數(shù)據(jù)展示到頁面上。那我們就先將其effect出來,在讀取值的時(shí)候執(zhí)行effect。
class ComputedRefImpl { public readonly effect private _value// get和set需要使用的同一個(gè)值 constructor(getter, private readonly _setter) { this.effect = new ReactiveEffect(getter, () => { }) } get value() { this._value = this.effect.run() // 執(zhí)行 return this._value } set value(newValue) { this._setter(newValue) } }
這樣頁面的數(shù)據(jù)就可以正常展示了。
but,我們卻忽略了一個(gè)問題,在多次讀取時(shí),其實(shí)并沒有computed的緩存功能,只是effect正常獲取某個(gè)值,獲取一次執(zhí)行一次。
緩存功能
那么,我們要做的就是繼續(xù)完善computed
的緩存功能。
既然computed
也是響應(yīng)式的,那么它是一個(gè)effect,同時(shí)肯定也需要有一個(gè)緩存標(biāo)識,控制它的緩存特性。如果依賴的屬性發(fā)生變化,那么這個(gè)緩存標(biāo)識會更新,重新執(zhí)行g(shù)et,沒有就不重新執(zhí)行。
定義一個(gè)_dirty
,默認(rèn)應(yīng)該取值的時(shí)候進(jìn)行計(jì)算。一開始為true,默認(rèn)為新值,更新。當(dāng)執(zhí)行完更新的時(shí)候應(yīng)該將其置稱false,為false時(shí)就默認(rèn)不執(zhí)行更新。
那么除了需要在get讀取值的時(shí)候進(jìn)行判斷,我們還需要依賴屬性發(fā)生變化的時(shí)候,再去進(jìn)行判斷重新渲染,即在構(gòu)造函數(shù)傳入我們的一個(gè)調(diào)度回調(diào)scheduler
(我們上篇文章實(shí)現(xiàn)的調(diào)度函數(shù),在依賴屬性發(fā)生變化的時(shí)候,會執(zhí)行我們傳入的回調(diào)函數(shù))。
class ComputedRefImpl { public readonly effect private _value// get和set需要使用的同一個(gè)值 public _dirty = true // 默認(rèn)應(yīng)該取值的時(shí)候進(jìn)行計(jì)算 constructor(getter, private readonly _setter) { this.effect = new ReactiveEffect(getter, () => { //稍后依賴的屬性變化 就會執(zhí)行此調(diào)度函數(shù) if (!this._dirty) { this._dirty = true } }) } get value() { if (this._dirty) { // 臟數(shù)據(jù) this._dirty = false this._value = this.effect.run() // 執(zhí)行 } return this._value } set value(newValue) { this._setter(newValue) } }
我們可以看到,此時(shí)introduction
值未發(fā)生變化,就只會執(zhí)行一次effet。
computed 設(shè)置值實(shí)現(xiàn)
修改值,我們可以通過上述用法一來傳入一個(gè)帶有set和get函數(shù)的對象,在設(shè)置值的時(shí)候通過解構(gòu)的方式進(jìn)行賦值。
在 computed 中的set時(shí),我們可以拿到computed的新值,但并沒有重新渲染更新頁面。此時(shí)我們需要做的就是將computed相關(guān)的屬性進(jìn)行依賴的收集,并在發(fā)生變化的時(shí)候進(jìn)行相應(yīng)的依賴觸發(fā)。與effect類似。
既然類似effect,那我們就需要一個(gè)存放依賴的dep(在類中定義public dep = undefined
),get時(shí)收集,屬性變化時(shí)觸發(fā)。
// ComputedRefImpl 類 get value() { trackEffects(this.dep || (this.dep = new Set())) // 收集 if (this._dirty) { // 臟數(shù)據(jù) this._dirty = false this._value = this.effect.run() // 執(zhí)行 } return this._value }
// ComputedRefImpl 類 constructor(getter, private readonly _setter) { this.effect = new ReactiveEffect(getter, () => { //稍后依賴的屬性變化 就會執(zhí)行此調(diào)度函數(shù) if (!this._dirty) { this._dirty = true // 觸發(fā)更新 triggerEffects(this.dep) } }) }
其中收集trackEffects
和觸發(fā)triggerEffects
就是將effect中的track
和trigger
中部分共用功能提取出來了。
比如收集的這個(gè)功能trackEffects
。與effect類似,我們都需要將記錄相應(yīng)的依賴屬性和依賴屬性的effect,那我們就可以將其獨(dú)立成一個(gè)新的功能函數(shù),降低耦合度。
export function track(target, type, key) { // 收集effect中 屬性對應(yīng)的effect if (!activeEffect) return let depsMap = targetMap.get(target) if (!depsMap) { targetMap.set(target, (depsMap = new Map())) } let dep = depsMap.get(key) if (!dep) { depsMap.set(key, (dep = new Set())) } // 判斷去重 set中是否存在activeEffect trackEffects(dep) } // 將set存起來 export function trackEffects(dep) { if (!activeEffect) return let shouldTrack = !dep.has(activeEffect) // 不存在 if (shouldTrack) { dep.add(activeEffect) // 屬性記錄effect // 反向記錄 effect記錄哪些屬性收集過 activeEffect.deps.push(dep) // 讓activeEffect 記錄住對應(yīng)的dep 稍后清理會用到 } }
triggerEffects
也是如此:
export function trigger(target, type, key, value, oldValue) { // 判斷targetMap是否存在target // 不存在 直接返回 不需要收集 // 存在 取depsMap中對應(yīng)key的effect 執(zhí)行run const depsMap = targetMap.get(target) if (!depsMap) return let effects = depsMap.get(key) if (effects) { triggerEffects(effects) } } export function triggerEffects(effects) { effects = [...effects] // effects 中 set結(jié)構(gòu)刪除再添加會導(dǎo)致死循環(huán) effects.forEach(effect => { // 在執(zhí)行effect時(shí),又要執(zhí)行自己,需要屏蔽自己的effect if (effect !== activeEffect) { if (effect.scheduler) effect.scheduler() // 如果存在自己的調(diào)度函數(shù)就執(zhí)行自己的scheduler else effect.run() // 否則就執(zhí)行run } }); }
上述只是vue3中computed的簡版實(shí)現(xiàn)方式,源碼中比我們實(shí)現(xiàn)的考慮的要多很多,有興趣的可以自己去看看(源碼路徑:packages/reactivity/src/computed.ts):
最后,我們也可以看到,頁面上過了1秒之后,相應(yīng)的屬性變化,頁面也同樣發(fā)生了變化??????。
其實(shí),computed
的核心就是effect + 緩存 + 依賴收集、觸發(fā)
以上就是Vue3 computed初始化獲取設(shè)置值實(shí)現(xiàn)示例的詳細(xì)內(nèi)容,更多關(guān)于Vue3 computed值設(shè)置獲取的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue項(xiàng)目打包解決靜態(tài)資源無法加載和路由加載無效(404)問題
這篇文章主要介紹了vue項(xiàng)目打包,解決靜態(tài)資源無法加載和路由加載無效(404)問題,靜態(tài)資源無法使用,那就說明項(xiàng)目打包后,圖片和其他靜態(tài)資源文件相對路徑不對,本文給大家介紹的非常詳細(xì),需要的朋友跟隨小編一起看看吧2023-10-10vue3成功創(chuàng)建項(xiàng)目后?run?serve啟動項(xiàng)目報(bào)錯(cuò)的解決
這篇文章主要介紹了vue3成功創(chuàng)建項(xiàng)目后?run?serve啟動項(xiàng)目報(bào)錯(cuò)的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03vue中for循環(huán)更改數(shù)據(jù)的實(shí)例代碼(數(shù)據(jù)變化但頁面數(shù)據(jù)未變)
這篇文章主要介紹了vue中for循環(huán)更改數(shù)據(jù)的實(shí)例代碼(數(shù)據(jù)變化但頁面數(shù)據(jù)未變)的相關(guān)資料,需要的朋友可以參考下2017-09-09vue-resource post數(shù)據(jù)時(shí)碰到Django csrf問題的解決
這篇文章主要介紹了vue-resource post數(shù)據(jù)時(shí)碰到Django csrf問題的解決,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03vue使用高德地圖實(shí)現(xiàn)添加點(diǎn)標(biāo)記和獲取點(diǎn)擊位置信息的示例代碼
這篇文章主要介紹了vue使用高德地圖實(shí)現(xiàn)添加點(diǎn)標(biāo)記和獲取點(diǎn)擊位置信息的示例代碼,文中補(bǔ)充介紹了高德vue-amap使用(一)標(biāo)記點(diǎn)位獲取地址及經(jīng)緯度,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2024-01-01