- 原文地址: TC39 Standards Track Decorators in Babel
- 原文作者: Nicolò Ribaudo
- 譯文出自: Babel 中文網 · 印記中文
- 譯者: QC-L
- 校對者: dear-lizhihua, hijiangtao
歡迎拍磚,協助我們更好的建設中文社群。
Babel 7.1.0 最終支援了新的裝飾器提案:你可以使用 @babel/plugin-proposal-decorators
外掛來提前嘗試此功能 ?。
相關歷史
三年多以前,Yehuda Katz 首先提出了裝飾器的概念。TypeScript 在 1.5 版本(2015)中釋出了對裝飾器的支援以及許多 ES6 的相關特性。 一些主流框架,如 Angular 和 MobX 等開始使用它們來增加開發者體驗:這使得裝飾器非常受歡迎,並給社群帶來了一種已經穩定的錯覺。
Babel 第一次實現裝飾器是在 v5 版本中,但由於該提案仍在不斷變化,則在 Babel v6 中移除了它們。Logan Smyth 建立了一個非官方的外掛(babel-plugin-transform-decorators-legacy
),它延用了 Babel 5 中裝飾器的行為;在 Babel 7 的 alpha 版本釋出期間該庫被移至 Babel 官方的倉庫中。當時該外掛仍使用舊的裝飾器語法,因為新提案尚未明確。
自那時起,Daniel Ehrenberg、Brian Terlson 以及 Yehuda Katz 就一起成為了該提案的共同作者,該提案几乎已被完全重寫。當然並非一切事情都已確定,因為至今尚未出現符合規範的實現方式。
Babel 7.0.0 為 @babel/plugin-proposal-decorators
外掛引入了新的標識:legacy
選項,其唯一有效值為 true
。這種突破性變更是必要的,它為提案從第一階段到當前階段平穩過渡作鋪墊。
在 Babel 7.1.0 中,我們引入了對這個新提案的支援,並且當 @babel/plugin-proposal-decorators
外掛被使用時,預設啟用。而在 Babel 7.0.0 中如果我們不設定 legacy: true
選項,預設情況下就不能使用該語義(相當於 legacy: false
)。
新提案同時支援使用裝飾器實現私有欄位(private fields)和私有方法(private methods)。我們尚未在 Babel 中實現此功能(在每個 class 中使用裝飾器或私有元素),但我們會很快去出現它。
新提案有何變化?
儘管新提案看起來與舊提案非常相似,但還是有幾個重要的差異使得它們互不相容。
語法
舊提案允許任何有效的左表示式(字面量,函式,類表示式,new 表示式以及函式呼叫等)用作裝飾器主體。有效程式碼如下所示:
class MyClass {
@getDecorators().methods[name]
foo() {}
@decorator
[bar]() {}
}
複製程式碼
該語法存在問題:[...]
符號在裝飾器內被用作屬性訪問及定義計算名稱。為了防止這種歧義出現,新提案只允許通過點屬性訪問(foo.bar
)可以選擇在引數末尾使用(foo.bar()
)。如果需要使用很複雜的表示式,可以將它們包裹在括號內:
class MyClass {
@decorator
@dec(arg1, arg2)
@namespace.decorator
@(complex ? dec1 : dec2)
method() {}
}
複製程式碼
物件裝飾器
舊提案允許除類和類元素裝飾器以外的物件成員使用裝飾器:
const myObj = {
@dec1 foo: 3,
@dec2 bar() {},
};
複製程式碼
由於與當前物件字面量語義的某些不相容性,它們已從提案中被移除。如果你的程式碼中使用了它們,請繼續關注,因為它們可能會在後續提案中被重新引入。(tc39/proposal-decorators#119)
裝飾器函式相關引數
新提案提出的第三個重要變化與傳遞給裝飾器函式引數相關。
在提案的第一個版本中,類元素裝飾器接收的引數分別為目標類(或物件),key 以及屬性描述符 - 與傳遞給 Object.defineProperty
的形式類似。類裝飾器將目標建構函式(constructor)作為唯一引數。
新的裝飾器提案更加強大:元素裝飾器會接收一個物件,該物件除更改屬性描述符外,還允許更改 key 值,可以賦值(static
,prototype
或者 own
),以及元素的型別(field
或 method
)。它們還可以建立其他屬性並在裝飾類上定義執行函式(完成器)。
類裝飾器接收一個包含類描述符的物件,使得類在建立之前修改它們成為可能。
升級
鑑於這些不相容性問題,新提案中不可能使用現有的裝飾器:這將使得遷移變得緩慢,因為現有庫(MobX,Angular等)無法在不引入這些突破性變化的情況下進行升級。 為解決此問題,我們釋出了實用工具包,它將裝飾器包裝在你的程式碼當中。執行後, 你可以安心的更改你的 Babel 配置以便使用新提案 ?。
使用如下命令來升級你的檔案:
npx wrap-legacy-decorators src/file-with-decorators.js --decorators-before-export --write
複製程式碼
如果你的程式碼僅在 Node 中執行,或者你會使用 webpack 或 rollup 構建你的程式碼,則需要使用外部依賴項(external dependency),避免在每個檔案中注入包裝函式:
npm install --save decorators-compat
npx wrap-legacy-decorators src/file-with-decorators.js --decorators-before-export --external-helpers --write
複製程式碼
欲瞭解更多資訊,請參閱工具包(package)相關文件.
開放問題
並非所有內容都已確定:裝飾器是一個非常強大的功能,想要將裝飾器定義為最好的表現形式,是相當複雜的。
匯出類(exported class)裝飾器如何放置?
該問題在裝飾器提案中反覆被問到:裝飾器應該放置在關鍵字 export 前還是關鍵字 export 後?
export @decorator class MyClass {}
// 或者
@decorator
export class MyClass {}
複製程式碼
根本問題是 export
關鍵字是否是類宣告的一部分,或者它是否是一個"包裝器"。第一種情況下,它應該在裝飾器之後,因為裝飾器出現在宣告的起始位置;第二種情況下,它應該在裝飾器之前,因為裝飾器是類宣告的一部分。
如何讓裝飾器與私有元素安全地互動?
裝飾器引發了一個重要的安全隱患:如果裝飾私有元素,那麼私有名稱(可以視為私有元素的 "key")可能會被洩露。有不同的安全級別需要考慮:
- 裝飾器有意外洩露私有名稱的風險。惡意程式碼不應該以任何方式從其他裝飾器中"竊取"私有名稱。
- 只有直接應用於私有元素的裝飾器才被視為可信任:類裝飾器是不是不應該讀寫私有元素?
- 高度隱私 (class fields 提案的目標之一) 意味著私有元素只能從類內部訪問:是否需要讓任何裝飾器都可以訪問私有名稱?是否應該只裝飾公共元素?
這些問題需要在解決之前進一步討論,這正是 Babel 所存在的意義。
Babel 的作用
遵循 What's Happening With the Pipeline (|>) Proposal? 文章中的走向,隨著 Babel 7 的釋出,我們開始利用我們在 JS 生態系統中的地位,通過讓開發人員能夠測試提案的不同變體,根據他們給出的反饋來幫助提案的作者完善提案。
出於這樣的角度,隨著 @babel/plugin-proposal-decorators
的更新,我們引入了新的選項:decoratorsBeforeExport
,它允許使用者嘗試使用 export @decorator class C {}
和 @decorator export default class
。
我們還將採用一個選項來定製裝飾器私有元素的隱私約束。使用該選項是必要的,直到 TC39 人員對它們做出選擇,由此就可以讓預設行為指定為最終提案中的內容。
如果你直接使用 (@babel/parser
,之前的 babylon
),你可以在 7.0.0 版本中使用 decoratorsBeforeExport
選項:
const ast = babylon.parse(code, {
plugins: [
["decorators", { decoratorsBeforeExport: true }]
]
})
複製程式碼
用法
用於 Babel 本身:
npm install @babel/plugin-proposal-decorators --save-dev
複製程式碼
{
"plugins": ["@babel/plugin-proposal-decorators", { "decoratorsBeforeExport": true }]
}
複製程式碼
查閱 @babel/plugin-proposal-decorators
文件以獲取更多相關選項。
你的作用
作為 JavaScript 開發者,你可以幫助規劃改語言的未來。你可以為裝飾器考慮各種語義環境同時進行測試,並向提案的作者提出反饋。我們需要知道你在真實專案環境中是如何使用它們的!你還可以通過閱讀提案倉庫中的 issues 討論及會議記錄來找出為什麼最終做出這樣的設計決策。
如果想立即嘗試裝飾器,可以使用我們的 repl 配置不同的 preset 選項進行試用!
關注我們
掃碼關注我們的公眾號,我們會定期推送一下社群相關的文章和動態。