最新,Vue 中的響應性語法糖已廢棄
theme: nico
介紹
自從引入組合式 API 的概念以來,一個主要的未解決的問題就是 ref
和 reactive
到底用哪個。reactive
存在解構丟失響應性的問題,而 ref
需要到處使用 .value
則感覺很繁瑣,並且在沒有型別系統的幫助時很容易漏掉 .value
。
例如,下面的計數器:
html
<template>
<button @click="increment">{{ count }}</button>
</template>
使用 ref
定義 count
變數和 increment
方法:
```js
let count = ref(0)
function increment() {
count.value++
}
而使用響應性語法糖,我們可以像這樣書寫程式碼:
js
let count = $ref(0)
function increment() {
count++
}
``
1. Vue 的響應性語法糖是一個編譯時的轉換步驟,
$ref()方法是一個**編譯時的巨集命令**,它不是一個真實的、在執行時會呼叫的方法,而是用作 Vue 編譯器的標記,表明最終的
count變數需要是一個**響應式變數**。
2. 響應式的變數可以像普通變數那樣被訪問和重新賦值,但這些操作在編譯後都會變為帶
.value的
ref。所以上面例子中的程式碼也會被編譯成使用
ref定義的語法。
3. 每一個會返回
ref的響應式 API 都有一個相對應的、以
$` 為字首的巨集函式。包括以下這些 API:
- ref -> $ref
- computed -> $computed
- shallowRef -> $shallowRef
- customRef -> $customRef
- toRef -> $toRef
- 可以使用
$()
巨集來將現存的ref
轉換為響應式變數。js const a = ref(0) let count = $(a) count++ console.log(a.value) // 1
- 可以使用
$$()
巨集來將任何對響應式變數的引用都會保留為對相應ref
的引用。js let count = $ref(0) console.log(isRef($$(count))) // true
$$()
也適用於已解構的props
,因為它們也是響應式的變數。編譯器會高效地通過toRef
來做轉換:js const { count } = defineProps<{ count: number }>() passAsRef($$(count))
配置
響應性語法糖是 組合式 API 特有的功能,且必須通過構建步驟使用。
1. 必須,需要 @vitejs/plugin-vue@>=2.0.0
,將應用於 SFC 和 js(x)/ts(x) 檔案。
ts
// vite.config.js
export default {
plugins: [
vue({
reactivityTransform: true
})
]
}
- 注意 reactivityTransform
現在是一個外掛的頂層選項,而不再是位於 script.refSugar
之中了,因為它不僅僅只對 SFC 起效。
如果是 vue-cli
構建,需要 vue-loader@>=17.0.0
,目前僅對 SFC 起效。
js
// vue.config.js
module.exports = {
chainWebpack: (config) => {
config.module
.rule('vue')
.use('vue-loader')
.tap((options) => {
return {
...options,
reactivityTransform: true
}
})
}
}
如果是 webpack
+ vue-loader
構建,需要 vue-loader@>=17.0.0
,目前僅對 SFC 起效。
js
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
reactivityTransform: true
}
}
]
}
}
2. 可選,tsconfig.json
檔案中新增如下程式碼, 不然會報錯 TS2304: Cannot find name '$ref'.
,雖然不影響使用,但是會影響開發體驗:
json
"compilerOptions":{ "types": ["vue/ref-macros"] }
3. 可選,eslintrc.cjs
檔案中新增如下程式碼,不然會提示 ESLint: '$ref' is not defined.(no-undef)
:
js
module.exports = { ...globals: {
$ref: "readonly",
$computed: "readonly",
$shallowRef: "readonly",
$customRef: "readonly",
$toRef: "readonly",
}
};
4. 當啟用響應性語法糖時,這些巨集函式都是全域性可用的、無需手動匯入。也可以在 vue 檔案中顯式引入 vue/macros
,這樣就不用配置第二和第三步中的 tsconfig.json
和 eslintrc
了。
```js
import { $ref } from 'vue/macros'
let count = $ref(0) ```
已廢棄的實驗性功能
- 響應性語法糖曾經是一個實驗性功能,且已被廢棄,請閱讀廢棄原因。
- 在未來的一個小版本更新中,它將會從 Vue core 中被移除。如需繼續使用,請通過 Vue Macros 外掛。
廢棄原因
尤雨溪在2個星期前(2023 年 2 月 21 日上午 10:05 GMT+8),親自給出了廢棄的原因,翻譯如下:
正如你們中的許多人已經知道的那樣,我們在團隊一致同意的情況下正式放棄了這個 RFC。
理由
Reactivity Transform 的最初目標是通過在處理反應狀態時提供更簡潔的語法來改善開發人員的體驗。我們將其作為實驗性產品釋出,以收集來自現實世界使用情況的反饋。儘管提出了這些好處,我們還是發現了以下問題:
1. 失去 .value
使得更難分辨正在跟蹤的內容以及哪條線觸發了反應效果。這個問題在小型 SFC 中並不那麼明顯,但在大型程式碼庫中,心理開銷變得更加明顯,特別是如果語法也在 SFC 之外使用。
2. 由於 (1),一些使用者選擇僅在 SFC 內部使用 Reactivity Transform,這會在不同心智模型之間造成不一致和上下文轉換成本。因此,困境在於僅在 SFC 內部使用它會導致不一致,但在 SFC 外部使用它會損害可維護性。
3. 由於仍然會有外部函式期望使用原始引用,因此反應變數和原始引用之間的轉換是不可避免的。這最終增加了更多的學習內容和額外的精神負擔,我們注意到這比普通的 Composition API 更讓初學者感到困惑。
最重要的是,碎片化的潛在風險。儘管這是明確的選擇加入,但一些使用者對該提議表示強烈反對,原因是他們擔心他們將不得不與不同的程式碼庫一起工作,在這些程式碼庫中,有些人選擇了使用它,而有些人則沒有。這是一個合理的擔憂,因為 Reactivity Transform 需要一種不同的心智模型,它會扭曲 JavaScript 語義(變數賦值能夠觸發反應效果)。
考慮到所有因素,我們認為將其作為一個穩定的功能使用會導致問題多於收益,因此不是一個好的權衡。
遷移計劃
- 該功能已經通過 Vue Macros 以外部包的形式得到支援。
- 3.3:該功能將被標記為已棄用。它將繼續工作,但您應該在此期間遷移到 Vue Macros。
- 3.4:該功能將從核心中刪除,除非使用 Vue Macros,否則將不再有效。
留言
- 雖然 Reactivity Transform 會從官方包中移除,但我認為這是一個很好的嘗試。
- 寫得好。我喜歡詳細的 RFC 和基於使用者反饋的客觀評估。最後的結論很有道理。不要讓完美成為優秀的敵人。
- 雖然我很享受這個功能帶來的便利,但我在實際使用中確實發現了這個潛在的碎片問題。在未來的版本中刪除此功能可能不太情願,但工程師應該認真對待。🙂
- 您是刪除所有功能還是僅刪除
ref.value
進行轉換的部分?響應式props
解構呢,它會留下來嗎? - 我一直在將它用於中等規模的電子商務網站,沒有任何問題。我理解刪除它背後的基本原理,但在實踐中我發現它確實是一個很大的改進。所以我的問題是:現在怎麼辦?
- 是否建議那些討厭
.value
的人現在儘可能避免使用ref()
並像以前那樣使用reactive()
? .value
是必要的複雜性。就像任何其他響應式庫xxx.set()
一樣。- 建立一個轉換所有 Reactivity Transform 程式碼的包應該很容易吧?我也喜歡按照推薦的方式做事。
- ...