日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 前端技术 > vue >内容正文

vue

vue 同时执行两个函数 点击_【第2112期】 import { reactive } from #39;vue#39;

發(fā)布時(shí)間:2025/3/19 vue 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 vue 同时执行两个函数 点击_【第2112期】 import { reactive } from #39;vue#39; 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

今日早讀文章由@Anthony Fu授權(quán)分享。

@Anthony Fu,是 Vue 的 Core Team 的一員,在 Vue主要負(fù)責(zé) @vue/composition-api 這個(gè)項(xiàng)目的維護(hù)。這是一個(gè)面向 Vue 2 的插件,它在 Vue 2 中增加了 Vue 3 的 Composition API 的支持。最近也加入了 Vite 負(fù)責(zé)一些 Code Review 的工作。GitHub:@antfu

正文從這開(kāi)始~~

介紹

我這次分享的主要會(huì)和大家簡(jiǎn)單介紹一下響應(yīng)式與組合式 API,然后通過(guò)一個(gè)例子的形式介紹組合式 API 所帶來(lái)的優(yōu)勢(shì)。再來(lái),我會(huì)以一個(gè)工具庫(kù)作者的角度跟大家聊一聊如何做到 Vue 2 與 Vue 3 雙版本同時(shí)兼容的同構(gòu)。最后,我會(huì)去再介紹一下響應(yīng)式 API 的一些延伸應(yīng)用。

慶祝 Vue 3.0 One Piece 在上個(gè)禮拜正式發(fā)布!

大家知道,在 Vue 3.0 中我們使用 TypeScript 進(jìn)行了一次從零的重寫(xiě)。利用這次重寫(xiě)的機(jī)會(huì),我們對(duì)整個(gè) Repo 的結(jié)構(gòu)進(jìn)行了一些解構(gòu),把 Vue 拆分成了這幾個(gè)獨(dú)立的庫(kù)。在這一次的分享中我會(huì)主要會(huì)面向比較底層的響應(yīng)式(@vue/reactivity)和組合式(@vue/runtime-core)這兩個(gè)模塊進(jìn)行討論。

響應(yīng)式 Reactivity API

那么什么是響應(yīng)式呢?提到這個(gè)就得祭出這張非常經(jīng)典的 GIF。在一個(gè) Excel 表格里面,我們會(huì)以公示的形式去定義一個(gè)一個(gè)單元格應(yīng)該去做怎么樣的一個(gè)運(yùn)算。那么大家可以看到,在我設(shè)置好了 A3 這個(gè)格子的公式之后,我去更新 A1 的數(shù)值時(shí), A3 就會(huì)自動(dòng)更新,而我不需要再去做任何的操作。這就是響應(yīng)是能夠給我們帶來(lái)的一個(gè)非常好的幫助,依賴(lài)的自動(dòng)收集跟更新。

在 Vue 3 里面,我們對(duì)整個(gè)響應(yīng)式系統(tǒng)做了一個(gè)重新的設(shè)計(jì),同時(shí)暴露出了這幾個(gè)新的API,ref reactive computed effect。我們把原本 Vue 2 Object.defineProperty 的實(shí)現(xiàn)改成了使用 Proxy 的實(shí)現(xiàn)方式。而 Proxy 可以給我們提供對(duì)屬性更新監(jiān)控的更大的靈活性。

const reactive = (target) => new Proxy(target, {

get(target, prop, receiver) {

track(target, prop)

return Reflect.get(...arguments) // get original data

},

set(target, key, value, receiver) {

trigger(target, key)

return Reflect.set(...arguments)

}

})

const obj = reactive({

hello: 'world'

})

console.log(obj.hello) // `track()` get called

obj.hello = 'vue' // `trigger()` get called

我們可以通過(guò) get 和 set 這兩個(gè) handler 去追蹤每一個(gè)屬性的訪問(wèn)和修改,在這個(gè)例子中我們?cè)?get 里注入了 track 這個(gè)函數(shù),在 set 里注入了trigger 這個(gè)函數(shù)。那么在對(duì) reactive 這個(gè)對(duì)象的 hello 屬性進(jìn)行訪問(wèn)的時(shí)候 track 就會(huì)被執(zhí)行,在對(duì) obj.hello 進(jìn)行賦值的時(shí)候,trigger 就會(huì)被執(zhí)行。通過(guò) track 和 trigger 我們就可以進(jìn)行一些響應(yīng)式的追蹤。

Effect

effect 是在 Vue 3 里面新引入的一個(gè)API,它的作用就是去結(jié)合 track 和 trigger 這兩個(gè)功能,track 的作用是追蹤調(diào)用他的函數(shù),trigger 是去觸發(fā)綁定的依賴(lài)更新。

const targetMap = new WeakMap()

export const track = (target, key) => {

if (tacking && activeEffect)

targetMap.get(target).key(key).push(activeEffect)

}

export const trigger = (target, key) => {

targetMap.get(target).key(key).forEach(effect => effect())

}

export const effect = (fn) => {

let effect = function() { fn() }

enableTracking()

activeEffect = effect

fn()

resetTracking()

activeEffect = undefined

}

在 effect 里面我們會(huì)接受一個(gè)函數(shù)作為參數(shù),在執(zhí)行這個(gè)函數(shù)之前的我們會(huì)開(kāi)啟 tracking,然后把當(dāng)前的函數(shù)設(shè)置在一個(gè)全局變量 activeEffect,然后再去執(zhí)行這個(gè)函數(shù)。那么在這個(gè)函數(shù)的調(diào)用時(shí)間里面我們有任何的 reactive 的調(diào)用就會(huì)觸發(fā) track 這個(gè)函數(shù)。track 的主要功能就是說(shuō)我們把當(dāng)前的 activeEffect 綁定到所觸發(fā)它的這個(gè)屬性調(diào)用上。然后在數(shù)據(jù)更新的時(shí)候,我們?cè)偃フ业竭@個(gè)依賴(lài)上面所綁定的所有 effect 把他們一一調(diào)用。這樣就完成了一個(gè)最基本的響應(yīng)式的功能。

computed & watch

在 Vue 3.0 里面,computed 和 watch 都是基于 effect 的包裝,我們這邊可以看到一個(gè)簡(jiǎn)單的 computed 的實(shí)現(xiàn)

const computed = (getter) => {

let value

let dirty = true

const runner = effect(getter, {

lazy: true,

scheduler() {

dirty = true // deps changed

}

})

return {

get value() {

if (dirty) {

value = runner() // re-evaluate

dirty = false

}

return value

}

}

}

computed 接受一個(gè) getter 函數(shù),這個(gè)函數(shù)我們把它直接傳給 effect,effect會(huì)在先執(zhí)行一次進(jìn)行依賴(lài)收集,在收集完了之后,如果里面其中的依賴(lài)發(fā)生了變動(dòng),他就會(huì)觸發(fā)這個(gè) scheduler 將 dirty 設(shè)置為 true。在最后我們?cè)趯?duì) computed 進(jìn)行求值的時(shí)候,如果 dirty 為 true,我們就會(huì)重新進(jìn)行一次運(yùn)算得到新的 value 后再把 value 傳出去。在第二次調(diào)用時(shí),如果里面的依賴(lài)沒(méi)有更新,我們就可以直接用上一次計(jì)算的結(jié)果,這件可以避免掉多余重復(fù)的計(jì)算。這里有一些 延伸閱讀,大家如果有興趣去了解一些比較深入的原理的話也可以去看一看。

組合式 Composition API

那么聊完了響應(yīng)式,我們?cè)賮?lái)看看什么是組合式。

組合式其實(shí)是基于響應(yīng)式延伸出來(lái)的一套和 Vue 生命周期綁定的一套工具。它提供了 Vue 生命周期的鉤子像是 onMounted onUpdate 和 onUnmounted 等等。還有個(gè)非常重要的功能就是說(shuō)在 Vue 的 setup() 里面,所建立的類(lèi)似 computed 或者 watch 的 effect 會(huì)在組件銷(xiāo)毀的時(shí)候自動(dòng)跟隨這個(gè)組件一并銷(xiāo)毀。那么組合是最重要的作用就是它可以提供可復(fù)用的邏輯,我們可以把很多的邏輯拆分出來(lái),做成一個(gè)一個(gè)的工具。然后可以跨組件的進(jìn)行復(fù)用或甚至是把它做成一個(gè)第三方庫(kù),跨應(yīng)用地進(jìn)行復(fù)用。這個(gè)我們會(huì)在之后進(jìn)行詳細(xì)的介紹。

響應(yīng)式是跟組合式的區(qū)別,就是他們是有兩個(gè)不同的包提供的,在整個(gè) Vue 應(yīng)用的角度來(lái)看的話 ,這些 API 都會(huì)從 vue 這個(gè)包里面統(tǒng)一導(dǎo)出的。但是如果我們會(huì)我們想要使用其中的一部分的話,那么可以看到 ref reactive computed effect 是在 @vue/reactivity 這個(gè)包里導(dǎo)出的,然后像是 watch setup 和一些生命周期是在 @vue/runtime-core 這個(gè)包里導(dǎo)出的??梢宰⒁獾揭稽c(diǎn)也是非常有趣的一點(diǎn),就是 @vue/reactivity 這個(gè)包其實(shí)是可以作為一個(gè)獨(dú)立的包使用的,也就是說(shuō)我可以不依賴(lài)于 Vue,我可以基于這個(gè)自己做一個(gè)框架,甚至我可以在 Node.js,在沒(méi)有 UI 的環(huán)境下去進(jìn)行使用。這個(gè)也會(huì)在我們后面的PPT里面去做一個(gè)比較詳細(xì)的介紹。

Case Study

那我們來(lái)看一個(gè)簡(jiǎn)單的使用場(chǎng)景的一個(gè)例子,這里有一個(gè)需求,我們現(xiàn)在想給我們的網(wǎng)頁(yè)實(shí)現(xiàn)一個(gè) Dark Mode 這個(gè)功能。我希望整個(gè)頁(yè)面在默認(rèn)的情況下會(huì)隨著我系統(tǒng)的系統(tǒng)的偏好改變。然后我可能希望一個(gè)用戶有一個(gè)手動(dòng)可以修改的功能,比如說(shuō)我有一個(gè)按鈕一個(gè)直接改變 Dark Mode。

然后又希望這個(gè)這個(gè)功能是一個(gè)可持久化的,我可以保存下用戶的偏好,在網(wǎng)頁(yè)刷新后還可以還可以繼續(xù)存留用戶的上一次的修改。最后可能會(huì)希望說(shuō)在兩個(gè)模式切換的時(shí)候去執(zhí)行一些代碼,比如說(shuō)通知用戶或者是通知組件進(jìn)行一些操作之類(lèi)的。

基礎(chǔ)實(shí)現(xiàn)

那我們看一下我們?cè)趺慈?shí)現(xiàn)這樣一個(gè)功能。我們假設(shè)說(shuō) Dark Mode 已經(jīng)在CSS層面上都做好了,也就是說(shuō)我把 dark class 加上的時(shí)候,整個(gè)頁(yè)面就會(huì)變成黑暗模式。那么我再提供一個(gè)按鈕去給用戶做切換。這個(gè)就是我們提供的模板的部分

:class='{dark}'>

@click='toggleDark'>Toggle

我們?cè)賮?lái)看代碼的部分要怎么實(shí)現(xiàn)

那么在 Options API 里面,非常的簡(jiǎn)單,我們可以這樣實(shí)現(xiàn):

export default {

data() {

return {

dark: false

}

},

methods: {

toggleDark() {

this.dark = !this.dark

}

}

}

那在 Composition API 里面,我們可以把 dark 變成 ref。這個(gè) dark 會(huì)直接從setup() 里面?zhèn)鞒鋈?#xff0c;那我們同時(shí)可以在 return 里面?zhèn)饕粋€(gè)叫做 toggleDark 的函數(shù),然后我們也是一樣對(duì) dark 進(jìn)行取反。這樣我們就實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的開(kāi)關(guān)的功能。

系統(tǒng)偏好

再來(lái)的話,我們希望去增加用戶系統(tǒng)偏好的更新。我們可以通過(guò)一個(gè)瀏覽器提供的 API window.matchMedia。然后再利用一個(gè) CSS 的 Query (prefers-color-scheme: dark),我們就可以知道是用戶的系統(tǒng)的顏色偏好。然后我們會(huì)我們可以對(duì)這個(gè) matchMedia 調(diào)用 addEvenetListener 進(jìn)行監(jiān)聽(tīng),那么在用戶系統(tǒng)改變的時(shí)候,我們可以隨之一起改變。

那么為了實(shí)現(xiàn)這樣一個(gè)功能的話,在 Options API 里面我們需要在需要將 media 暴露在 Vue 實(shí)例上,然后在 created 中進(jìn)行事件的綁定,同時(shí)在 destroyed 的時(shí)候再把這個(gè)事件監(jiān)聽(tīng)注銷(xiāo)。

// Options API

export default {

data() {

return {

dark: false,

media: window.matchMedia('(prefers-color-scheme: dark)')

}

},

methods: {

toggleDark() {

this.dark = !this.dark

},

update() {

this.dark = this.media.matches

}

},

created() {

this.media.addEventListener('change', this.update)

this.update()

},

destroyed() {

this.media.removeEventListener('change', this.update)

}

}

那么再來(lái)看看 Composition API 要怎么實(shí)現(xiàn)。我們直接定義這個(gè) media。

然后因?yàn)樵?Composition API 中,setup() 相當(dāng)于 Options API 的 created,我們直接可以把 addEventListener 的直接寫(xiě)在 setup() 里面,對(duì)應(yīng)的我們?cè)偻ㄟ^(guò)一個(gè)生命周期的鉤子 OnUnmounted 注銷(xiāo)事件監(jiān)聽(tīng)。

// Composition API

import { onUnmounted, ref } from 'vue'

export default {

setup() {

const media = window.matchMedia('(prefers-color-scheme: dark)')

const dark = ref(media.matches)

const update = () => dark.value = media.matches

media.addEventListener('change', update)

onUnmounted(() => {

media.removeEventListener('change', update)

})

return {

dark,

toggleDark() {

dark.value = !dark.value

}

}

}

}

用戶設(shè)置持久化

再來(lái)我們需要讓用戶的設(shè)置可以持久化,我們就需要把用戶的設(shè)置存在 localStorage 里。設(shè)置修改的時(shí)候存入 localStorage,每次頁(yè)面加載的時(shí)候再讀出來(lái)。邊代碼大家看一看就可以了,主要想讓大家看到的一點(diǎn)就是在 Options API 里面,我們給現(xiàn)有的一個(gè)組件增加功能的時(shí)候,我們會(huì)在不同的地方插入代碼。比如說(shuō)在 data 里面聲明狀態(tài),在 methods 加幾個(gè)函數(shù)。我們插入非常零碎的幾個(gè)片段去實(shí)現(xiàn)一個(gè)功能,當(dāng)這個(gè)組件的代碼變得非常的長(zhǎng)的時(shí)候我們很容易去丟失掉單一功能的上下文。

那么在 Composition API 里,我們可以我們可以很好的把代碼給組織在一起。像是這樣的一個(gè)功能,就只需要在一個(gè) Block 里面加入這些代碼,我們可以很清楚的上有上下文,也可以有 TypeScript 進(jìn)行檢查。 以我們剛剛實(shí)現(xiàn)的 Dark Mode 為例,其實(shí)相對(duì)并不是一個(gè)非常復(fù)雜的功能,而我們已經(jīng)寫(xiě)了這么多行的代碼。如果在再這個(gè)組件繼續(xù)的擴(kuò)展的時(shí)候,會(huì)導(dǎo)致代碼的整個(gè)結(jié)構(gòu)變得非常的復(fù)雜,其實(shí)就是一個(gè)不是非常好的 Smell。這也是我們希望避免的一件事情。

那么我們會(huì)可能會(huì)希望我們可以把邏輯拿出來(lái)復(fù)用,或者是我們希望 Dark Mode 的這個(gè)功能,可以在另外的一個(gè)組件去做調(diào)用,或者是我就希望讓整個(gè)代碼看起來(lái)比較的干凈。在 Options API 里面,我們是可以做到這一點(diǎn),但是現(xiàn)有的幾個(gè)方案都并不是非常的理想 (Mixin, Renderless Component, Vuex, etc.)

Mixin 問(wèn)題是會(huì)有命名空間的沖突。像是我們剛剛的例子,我們會(huì)有一個(gè) updated 的函數(shù),那么如果我們?cè)?Mixin 中使用 updated 這個(gè)函數(shù),然后用戶端在使用的時(shí)候如果沒(méi)有注意到,他也自己寫(xiě)了一個(gè) updated 函數(shù),這就會(huì)導(dǎo)致函數(shù)覆蓋,會(huì)出現(xiàn)一些不希望的情況,但是又很難去 debug。

Renderless Component 可以一定程度上解決命名空間的問(wèn)題,但是他只能在模板里面使用,組合性也有很多的局限。

Vuex 的話要做到這些就會(huì)變得更加復(fù)雜,你需要去定義 Mutations 也需要去定義 Actions。然后再綁定一些瀏覽器的事件。

但是 Composition API 的話就變得非常的簡(jiǎn)單粗暴,我只需要把 setup() 的代碼復(fù)制粘貼出去,然后用一個(gè)函數(shù)把它包裝起來(lái)。那么在這里,我就只需要去調(diào)一個(gè) use 就可以了。而且我們可以繼續(xù)在這里面寫(xiě)更多的邏輯,同時(shí)也不會(huì)導(dǎo)致找不到對(duì)應(yīng)的上下文。

進(jìn)一步復(fù)用

我們甚至可以進(jìn)行進(jìn)一步的復(fù)用。以剛剛的代碼為例,我們可以把這個(gè) useDark 里面的這個(gè) matchMedia 和用戶設(shè)置的部分把他單獨(dú)拉出來(lái),變成兩個(gè)獨(dú)立的獨(dú)立的函數(shù)。那么這些函數(shù)它就可以單獨(dú)去專(zhuān)注在解決他單一問(wèn)題上。以 useDark 的層面就只需要去在意,我在什么時(shí)候需要使用系統(tǒng)的設(shè)置和什么時(shí)候需要使用用戶的設(shè)置。這里還有一個(gè)有趣的點(diǎn),就是在這些組合工具里面他都可以使用生命周期的鉤子,它就可以做到自動(dòng)更新和自動(dòng)注銷(xiāo)。或者是說(shuō)在數(shù)據(jù)改變的時(shí)候自動(dòng)進(jìn)行保存。

那么做到這一點(diǎn)的情況下,在使用的時(shí)候就可以沒(méi)有什么負(fù)擔(dān)。我只需要去在意他每一個(gè) ref 對(duì)應(yīng)什么樣的功能,更新了之后它就可以幫我做到它應(yīng)該做到的事情。這樣對(duì)一個(gè)非常龐大的項(xiàng)目來(lái)說(shuō),可以更好的提高代碼的復(fù)用度也可以提高代碼的可讀性跟可維護(hù)性。

export function useDark() {

const system = usePreferDark()

const setting = useLocalStorage('setting-dark', 'auto')

const dark = computed({

get() {

return setting.value === 'auto'

? system.value

: setting.value === 'dark'

},

set(v) {

if (v === system.value)

setting.value = 'auto'

else

setting.value = v ? 'dark' : 'light'

},

})

return dark

}

export function usePreferDark() {

const media = window.matchMedia('(prefers-color-scheme: dark)')

const dark = ref(media.matches)

const update = () => dark.value = media.matches

media.addEventListener('change', update)

onUnmounted(() => {

media.removeEventListener('change', update)

})

return dark

}

export function useLocalStorage(key, defaultValue) {

const data = ref(localStorage.getItem(key) ?? defaultValue)

watch(data, () => localStorage.setItem(key, data.value))

return data

}

邏輯的組件

所以我覺(jué)得對(duì)于這些可以被復(fù)用的這些函數(shù)來(lái)說(shuō),它更像是一個(gè)邏輯的組件。我們平常講組件的時(shí)候,一般來(lái)說(shuō)都是指UI組件。UI 組件我們可以把它抽象成這樣一個(gè)情況,就是說(shuō) UI 組件接受一個(gè) Props,也就是從他的父組件傳進(jìn)來(lái)的一些參數(shù),然后會(huì)根據(jù)它的 State 去更新對(duì)應(yīng)的UI,再以通過(guò)事件的形式去通知父組件。

那么換到邏輯組件來(lái)說(shuō),其實(shí)就是一個(gè)函數(shù),函數(shù)可以接受一些參數(shù)。這些參數(shù)可以是普通參數(shù),也可以是響應(yīng)式的。然后在這些在這些函數(shù)里面,我們可以進(jìn)行一些生命周期的綁定,可以去做一些對(duì)監(jiān)聽(tīng)事件的銷(xiāo)毀。最后我再回傳出一些響應(yīng)式的數(shù)據(jù),這些數(shù)據(jù)可以是 ref 也可以是 reactive。同時(shí)這些響應(yīng)的數(shù)據(jù)會(huì)根據(jù)其中內(nèi)部的狀態(tài)進(jìn)行一些更新,可以達(dá)到類(lèi)似事件通知的效果。其實(shí)右邊這張圖是給 UI 組件的一張圖,但是我覺(jué)得他也同樣適用于邏輯組件。

也就是說(shuō),我可以復(fù)用底層的 useLocalStorage useQuery 去實(shí)現(xiàn)一個(gè)更高層的邏輯組件。讓每一層組件都專(zhuān)注于在做自己的事情上就好了。

現(xiàn)有邏輯組件庫(kù)

現(xiàn)有的 Vue 3 已經(jīng)可以使用的有兩個(gè)主要的邏輯的組件庫(kù),VueUse 和 vue-composable。有點(diǎn)像 React 中的 react-use 或者 ahooks 這一類(lèi)的工具。VueUse 提供了更加細(xì)粒度的 Web API 以及工具分裝。vue-composable 是由另外一個(gè) Core Team Member @pikax 做的,它提供了更多常用的邏輯封裝。例如 useI18n, useValidation 等等。這些功能直接實(shí)現(xiàn)在了這個(gè)工具里面,而不需要再去安裝另外依賴(lài)于別的庫(kù)的。

組合式 API 生態(tài)

然后和大家簡(jiǎn)單講一下組合式 API 的生態(tài)支持。在 DevTools 6.0.0-beta.2 的更新了之后,加入了 Vue 3 的支持,同時(shí)加入一個(gè)新的功能是 Timeline 這個(gè)自定義的事件的打點(diǎn),他可以去監(jiān)聽(tīng)整個(gè)應(yīng)用里面發(fā)生的各種各樣的事件,然后把它做成一個(gè)個(gè)的點(diǎn),讓你可以去以時(shí)間的維度知道發(fā)生了什么。

然后在 vue-composable 里面提供了一個(gè)非常有趣的 API 叫做 useDevtoolsInspector,你可以傳一些響應(yīng)式的數(shù)據(jù),當(dāng)這些數(shù)據(jù)更新的時(shí)候去打點(diǎn)在 Timeline。你就可以更好的知道你的這些響應(yīng)式的數(shù)據(jù)什么時(shí)候被什么時(shí)候被更新了以及更新成了什么。

import { useDevtoolsInspector } from 'vue-composable'

const counter = ref(0)

useDevtoolsInspector({ counter })

然后再來(lái)一個(gè)就是 SFC 的單文件組件的一些更新。我們給 script 標(biāo)簽加了一個(gè) setup 的 flag。那么通過(guò)

總結(jié)

以上是生活随笔為你收集整理的vue 同时执行两个函数 点击_【第2112期】 import { reactive } from #39;vue#39;的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。