vue key重復_【第2112期】 import { reactive } from #39;vue#39;

前言

今日早讀文章由@Anthony Fu授權分享。

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

正文從這開始~~

介紹

我這次分享的主要會和大家簡單介紹一下響應式與組合式 API,然后通過一個例子的形式介紹組合式 API 所帶來的優勢。再來,我會以一個工具庫作者的角度跟大家聊一聊如何做到 Vue 2 與 Vue 3 雙版本同時兼容的同構。最后,我會去再介紹一下響應式 API 的一些延伸應用。

慶祝 Vue 3.0 One Piece 在上個禮拜正式發布!

大家知道,在 Vue 3.0 中我們使用 TypeScript 進行了一次從零的重寫。利用這次重寫的機會,我們對整個 Repo 的結構進行了一些解構,把 Vue 拆分成了這幾個獨立的庫。在這一次的分享中我會主要會面向比較底層的響應式(@vue/reactivity)和組合式(@vue/runtime-core)這兩個模塊進行討論。

響應式 Reactivity API

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

在 Vue 3 里面,我們對整個響應式系統做了一個重新的設計,同時暴露出了這幾個新的API,ref reactive computed effect。我們把原本 Vue 2 Object.defineProperty 的實現改成了使用 Proxy 的實現方式。而 Proxy 可以給我們提供對屬性更新監控的更大的靈活性。

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

我們可以通過 get 和 set 這兩個 handler 去追蹤每一個屬性的訪問和修改,在這個例子中我們在 get 里注入了 track 這個函數,在 set 里注入了trigger 這個函數。那么在對 reactive 這個對象的 hello 屬性進行訪問的時候 track 就會被執行,在對 obj.hello 進行賦值的時候,trigger 就會被執行。通過 track 和 trigger 我們就可以進行一些響應式的追蹤。

Effect

effect 是在 Vue 3 里面新引入的一個API,它的作用就是去結合 track 和 trigger 這兩個功能,track 的作用是追蹤調用他的函數,trigger 是去觸發綁定的依賴更新。

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 里面我們會接受一個函數作為參數,在執行這個函數之前的我們會開啟 tracking,然后把當前的函數設置在一個全局變量 activeEffect,然后再去執行這個函數。那么在這個函數的調用時間里面我們有任何的 reactive 的調用就會觸發 track 這個函數。track 的主要功能就是說我們把當前的 activeEffect 綁定到所觸發它的這個屬性調用上。然后在數據更新的時候,我們再去找到這個依賴上面所綁定的所有 effect 把他們一一調用。這樣就完成了一個最基本的響應式的功能。

computed & watch

在 Vue 3.0 里面,computed 和 watch 都是基于 effect 的包裝,我們這邊可以看到一個簡單的 computed 的實現

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 接受一個 getter 函數,這個函數我們把它直接傳給 effect,effect會在先執行一次進行依賴收集,在收集完了之后,如果里面其中的依賴發生了變動,他就會觸發這個 scheduler 將 dirty 設置為 true。在最后我們在對 computed 進行求值的時候,如果 dirty 為 true,我們就會重新進行一次運算得到新的 value 后再把 value 傳出去。在第二次調用時,如果里面的依賴沒有更新,我們就可以直接用上一次計算的結果,這件可以避免掉多余重復的計算。這里有一些 延伸閱讀,大家如果有興趣去了解一些比較深入的原理的話也可以去看一看。

組合式 Composition API

那么聊完了響應式,我們再來看看什么是組合式。

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

響應式是跟組合式的區別,就是他們是有兩個不同的包提供的,在整個 Vue 應用的角度來看的話 ,這些 API 都會從 vue 這個包里面統一導出的。但是如果我們會我們想要使用其中的一部分的話,那么可以看到 ref reactive computed effect 是在 @vue/reactivity 這個包里導出的,然后像是 watch setup 和一些生命周期是在 @vue/runtime-core 這個包里導出的。可以注意到一點也是非常有趣的一點,就是 @vue/reactivity 這個包其實是可以作為一個獨立的包使用的,也就是說我可以不依賴于 Vue,我可以基于這個自己做一個框架,甚至我可以在 Node.js,在沒有 UI 的環境下去進行使用。這個也會在我們后面的PPT里面去做一個比較詳細的介紹。

Case Study

那我們來看一個簡單的使用場景的一個例子,這里有一個需求,我們現在想給我們的網頁實現一個 Dark Mode 這個功能。我希望整個頁面在默認的情況下會隨著我系統的系統的偏好改變。然后我可能希望一個用戶有一個手動可以修改的功能,比如說我有一個按鈕一個直接改變 Dark Mode。

然后又希望這個這個功能是一個可持久化的,我可以保存下用戶的偏好,在網頁刷新后還可以還可以繼續存留用戶的上一次的修改。最后可能會希望說在兩個模式切換的時候去執行一些代碼,比如說通知用戶或者是通知組件進行一些操作之類的。

基礎實現

那我們看一下我們怎么去實現這樣一個功能。我們假設說 Dark Mode 已經在CSS層面上都做好了,也就是說我把 dark class 加上的時候,整個頁面就會變成黑暗模式。那么我再提供一個按鈕去給用戶做切換。這個就是我們提供的模板的部分

:class='{dark}'>

@click='toggleDark'>Toggle

我們再來看代碼的部分要怎么實現

那么在 Options API 里面,非常的簡單,我們可以這樣實現:

export default {

data() {

return {

dark: false

}

},

methods: {

toggleDark() {

this.dark = !this.dark

}

}

}

那在 Composition API 里面,我們可以把 dark 變成 ref。這個 dark 會直接從setup() 里面傳出去,那我們同時可以在 return 里面傳一個叫做 toggleDark 的函數,然后我們也是一樣對 dark 進行取反。這樣我們就實現了一個簡單的開關的功能。

系統偏好

再來的話,我們希望去增加用戶系統偏好的更新。我們可以通過一個瀏覽器提供的 API window.matchMedia。然后再利用一個 CSS 的 Query (prefers-color-scheme: dark),我們就可以知道是用戶的系統的顏色偏好。然后我們會我們可以對這個 matchMedia 調用 addEvenetListener 進行監聽,那么在用戶系統改變的時候,我們可以隨之一起改變。

那么為了實現這樣一個功能的話,在 Options API 里面我們需要在需要將 media 暴露在 Vue 實例上,然后在 created 中進行事件的綁定,同時在 destroyed 的時候再把這個事件監聽注銷。

// 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)

}

}

那么再來看看 Composition API 要怎么實現。我們直接定義這個 media。

然后因為在 Composition API 中,setup() 相當于 Options API 的 created,我們直接可以把 addEventListener 的直接寫在 setup() 里面,對應的我們再通過一個生命周期的鉤子 OnUnmounted 注銷事件監聽。

// 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

}

}

}

}

用戶設置持久化

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

那么在 Composition API 里,我們可以我們可以很好的把代碼給組織在一起。像是這樣的一個功能,就只需要在一個 Block 里面加入這些代碼,我們可以很清楚的上有上下文,也可以有 TypeScript 進行檢查。 以我們剛剛實現的 Dark Mode 為例,其實相對并不是一個非常復雜的功能,而我們已經寫了這么多行的代碼。如果在再這個組件繼續的擴展的時候,會導致代碼的整個結構變得非常的復雜,其實就是一個不是非常好的 Smell。這也是我們希望避免的一件事情。

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

Mixin 問題是會有命名空間的沖突。像是我們剛剛的例子,我們會有一個 updated 的函數,那么如果我們在 Mixin 中使用 updated 這個函數,然后用戶端在使用的時候如果沒有注意到,他也自己寫了一個 updated 函數,這就會導致函數覆蓋,會出現一些不希望的情況,但是又很難去 debug。

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

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

但是 Composition API 的話就變得非常的簡單粗暴,我只需要把 setup() 的代碼復制粘貼出去,然后用一個函數把它包裝起來。那么在這里,我就只需要去調一個 use 就可以了。而且我們可以繼續在這里面寫更多的邏輯,同時也不會導致找不到對應的上下文。

進一步復用

我們甚至可以進行進一步的復用。以剛剛的代碼為例,我們可以把這個 useDark 里面的這個 matchMedia 和用戶設置的部分把他單獨拉出來,變成兩個獨立的獨立的函數。那么這些函數它就可以單獨去專注在解決他單一問題上。以 useDark 的層面就只需要去在意,我在什么時候需要使用系統的設置和什么時候需要使用用戶的設置。這里還有一個有趣的點,就是在這些組合工具里面他都可以使用生命周期的鉤子,它就可以做到自動更新和自動注銷。或者是說在數據改變的時候自動進行保存。

那么做到這一點的情況下,在使用的時候就可以沒有什么負擔。我只需要去在意他每一個 ref 對應什么樣的功能,更新了之后它就可以幫我做到它應該做到的事情。這樣對一個非常龐大的項目來說,可以更好的提高代碼的復用度也可以提高代碼的可讀性跟可維護性。

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

}

邏輯的組件

所以我覺得對于這些可以被復用的這些函數來說,它更像是一個邏輯的組件。我們平常講組件的時候,一般來說都是指UI組件。UI 組件我們可以把它抽象成這樣一個情況,就是說 UI 組件接受一個 Props,也就是從他的父組件傳進來的一些參數,然后會根據它的 State 去更新對應的UI,再以通過事件的形式去通知父組件。

那么換到邏輯組件來說,其實就是一個函數,函數可以接受一些參數。這些參數可以是普通參數,也可以是響應式的。然后在這些在這些函數里面,我們可以進行一些生命周期的綁定,可以去做一些對監聽事件的銷毀。最后我再回傳出一些響應式的數據,這些數據可以是 ref 也可以是 reactive。同時這些響應的數據會根據其中內部的狀態進行一些更新,可以達到類似事件通知的效果。其實右邊這張圖是給 UI 組件的一張圖,但是我覺得他也同樣適用于邏輯組件。

也就是說,我可以復用底層的 useLocalStorage useQuery 去實現一個更高層的邏輯組件。讓每一層組件都專注于在做自己的事情上就好了。

現有邏輯組件庫

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

組合式 API 生態

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

然后在 vue-composable 里面提供了一個非常有趣的 API 叫做 useDevtoolsInspector,你可以傳一些響應式的數據,當這些數據更新的時候去打點在 Timeline。你就可以更好的知道你的這些響應式的數據什么時候被什么時候被更新了以及更新成了什么。

import { useDevtoolsInspector } from 'vue-composable'

const counter = ref(0)

useDevtoolsInspector({ counter })

然后再來一個就是 SFC 的單文件組件的一些更新。我們給 script 標簽加了一個 setup 的 flag。那么通過

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/534186.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/534186.shtml
英文地址,請注明出處:http://en.pswp.cn/news/534186.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

matlab系統穩定性分析,控制系統穩定性分析的MATLAB實現

收稿日期 :200706220 基金項目 :周口師范學院青年基金資助項目(No. ZKNUQN200621) 作者簡介 :劉  偉(1976 - ) ,女 ,河南太康人 ,助教 ,碩士 ,主要從事電力系統及其自動化仿真研究. 第 25 卷 第 2 期 周口師范學院學報 2008 年 3 月 Vol. 25 No. 2 Journal of Zhoukou Normal …

路由器下一跳地址怎么判斷_網絡基本功三:細說路由器

介紹以太網交換機工作在第二層即數據鏈路層,用于在同一網絡內部轉發以太網幀。但是,當源和目的IP地址位于不同網絡時,以太網幀必須發送給路由器。路由器負責在不同網絡間傳輸報文,通過路由表來決定最佳轉發路徑。當主機將報文發送…

HTML多選mysql,html多選下拉框 | 學步園

一個jquery ui,實現html的多選下拉框,在下拉里面加checkbox,不改變頁面的提交特性,只是動態的改變select選中的多選數據。jsp頁面例子:pageEncoding"UTF-8" import"java.util.*,java.text.*"%>String path…

利用逆矩陣解線性方程組_經典Jacobi方法用于求解矩陣特征值

1、引言求解線性方程組在許多領域中都有重要應用,寫成矩陣的形式: 。求解 可以寫成: ,這里需要求解矩陣 的逆。《線性代數》中給出的方法主要有兩類:1、設置增廣矩陣,利用高斯消元法,通過初等行…

filename: core/loader.php,使用第三方包后出現的這個錯誤,你們都遇到過嗎?

使用了一些第三方包,經常會發現,引入某些第三方包后(比如在laravel5.6中引入viacreative/sudo-su),使用命令行工具會遇到這樣的錯誤提示,卸載了第三方包后重新安裝vendor目錄問題立馬解決。真是把人頭發都愁白了:PHP F…

python函數的作用域_python學習第五篇 函數 變量作用域

原博文 2019-07-18 23:40 ? 函數 函數是組合好的,可以重復使用的,用來實現單一或相關聯功能的代碼片段作用 能提高應用的模塊性和代碼的重復利用率函數的創建 第一函數的規則 1.函數代碼塊一def關鍵字開頭,后接函數標識符名稱和圓括號‘&…

js post中文亂碼 php,AJAX之POST數據中文亂碼如何解決

本文主要和大家分享AJAX之POST數據中文亂碼如何解決,前端使用encodeURI進行編碼,希望能幫助到大家。var param encodeURI(param);$.ajax({url: url,methodtype: "POST",async: false,timeout: 60000,contentType: "application/json&quo…

python遞歸 數字全排列_利用遞歸實現全排列(python)

利用遞歸實現全排列(python) """ 利用遞歸實現全排列 第一個位置可能有n種可能,第二個位置可能 有n-1種可能...... 代碼思路就是第一個位置可以和n個元素交換, 第二個元素可以和n-1個元素進行交換,到最 后一個輸出這次排列&am…

python pip使用_Python——pip的安裝與使用

pip 是 Python 包管理工具,該工具提供了對Python 包的查找、下載、安裝、卸載的功能。目前如果你在 python.org 下載最新版本的安裝包,則是已經自帶了該工具。Python 2.7.9 或 Python 3.4 以上版本都自帶 pip 工具。pip 官網:https://pypi.o…

php文章列表樣式,PHPCMS V9 文章列表循環樣式自定義方法

在此,再次分享Whidy的文章"phpcms文章列表循環不同樣式制作方法",下面CMSYOU來與大家具體分享,原地址為http://whidy.net/phpcms-list-with-different-style.html,在這里感謝。大家在用PHPCMS系統做網站的時候,有時候在…

角速度求積分能得到歐拉角嗎_一個有趣的反常積分問題

今天物理考試,老師提到了一個有趣的積分問題。聽說是拉普拉斯變換的一個應用之一(生成函數?),但是我沒聽過那個東西所以硬上了:D1)試求積分 2) 試說明積分 的收斂性1)對于第一問可以…

php計算1-100奇數的和,學習腳本1:計算100以內奇數和和偶數和 (筆記)

let I$[$I1]let I1let I 注意此處只有是原先數值加1才可用此方法上述三者運算是相同的- 減等 兩邊的變量前邊的減去后邊的變量之后把值再放到原來的變量上 加等 兩的的變量前邊的加上后邊的變量之后把值再放到原來的變量上* 乘等 兩邊的變量前邊的乘上后邊的變量之后把值再放到…

查看ie保存的表單_解決瀏覽器保存密碼自動填充問題

解決瀏覽器保存密碼自動填充問題問題描述話說有一天,我如往常一樣打開我的開發網站進行登錄操作。瀏覽器很平常的在我們進行登錄操作之后詢問我是否需要記住密碼,懶惰如我點擊了記住密碼。一切都很正常的進行著,沒有什么異常發生。然而&#…

java滿江紅1apk,滿江紅滿V版游戲下載_滿江紅滿V版安卓版游戲下載v1.0_3DM手游

喜歡玩精彩的傳奇游戲嗎?那就來《滿江紅滿V版》這款佳作中吧!這款手游操作方式極其的簡單,且玩法自由度也很高,咱們將會置身于一座很精美熱血的魔幻大陸中,各種大伙熟悉的人物職業可供收集培養,極致精彩的P…

go get 的不再src目錄中_GO語言基礎進階教程:包的使用

Go語言使用包(package)這種語法元素來組織源碼,所有語法可見性均定義在package這個級別,與Java 、python等語言相比,這算不上什么創新,但與C傳統的include相比,則是顯得“先進”了許多。myblog …

python mysql 正則表達式,MySQL之正則表達式(REGEXP)

MySQL中正則表達式通常被用來檢索或替換符合某個模式的文本內容,根據指定的匹配模式匹配文中符合要求的特殊字符串。例如,從一個文件中提取電話號碼,查找一篇文章中重復的單詞或替換用戶輸入的敏感語匯等,這些地方都可以使用正則表…

pyecharts anaconda_Pyecharts安裝使用和繪圖案例

一次偶然的機會,接觸了pyecharts,發現做圖交互效果非常棒,便深究、摸索、入坑。這篇文章主要講述自己在安裝和使用中遇到的問題,解決方法,最后還會有pyecharts中自己比較喜歡的繪圖功能。pyecharts是一款將python與ech…

控制附件的大小 php,wordpress如何修改默認上傳附件限制大小

關于上傳文件大小的限制,有很多有幾種情況,一是服務器上的限制(php.ini)php虛擬主機空間提供商為了保障服務器穩定、都會限制大容量附件上傳,在php.ini文件中做了限制,二是網站程序本身都會有限制大小,wp媒體文件大小默…

如何把密度函數化為標準正態二維分布_概率微課:第三章(22) 二維隨機變量及分布函數定義...

主要內容二維隨機變量及分布函數定義更多系列視頻概率微課:第二章(1) 隨機變量的定義概率微課:第二章(2) 離散型隨機變量概率微課:第二章(3) 兩點分布及伯努利試驗概率微課:第二章(4) 二項分布1概率微課:第二章(5) 二…

php中的緩,php中的緩存機制解釋

php緩存的理解,先列出ob系列函數的作用:ob_start(func) 開啟php緩存,回調函數是對緩存內數據的處理函數ob_gzhandler 作為 ob_start 的回調函數,對數據進行gz壓縮ob_implicit_flush(true/false) 打開或關閉apache緩存&#xff0c…