欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue3 computed初始化獲取設(shè)置值實(shí)現(xiàn)示例

 更新時(shí)間:2022年10月19日 16:06:18   作者:ClyingDeng  
這篇文章主要為大家介紹了Vue3 computed初始化以及獲取值設(shè)置值實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jì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中的tracktrigger中部分共用功能提取出來了。

比如收集的這個(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)文章

最新評論