- 原文地址: TC39 Standards Track Decorators in Babel
- 原文做者: Nicolò Ribaudo
- 譯文出自: Babel 中文網 · 印記中文
- 譯者: QC-L
- 校對者: dear-lizhihua, hijiangtao
歡迎拍磚,協助咱們更好的建設中文社區。javascript
Babel 7.1.0 最終支持了新的裝飾器提案:你可使用 @babel/plugin-proposal-decorators
插件來提早嘗試此功能 🎉。html
三年多之前,Yehuda Katz 首先提出了裝飾器的概念。TypeScript 在 1.5 版本(2015)中發佈了對裝飾器的支持以及許多 ES6 的相關特性。 一些主流框架,如 Angular 和 MobX 等開始使用它們來增長開發者體驗:這使得裝飾器很是受歡迎,並給社區帶來了一種已經穩定的錯覺。java
Babel 第一次實現裝飾器是在 v5 版本中,但因爲該提案仍在不斷變化,則在 Babel v6 中移除了它們。Logan Smyth 建立了一個非官方的插件(babel-plugin-transform-decorators-legacy
),它延用了 Babel 5 中裝飾器的行爲;在 Babel 7 的 alpha 版本發佈期間該庫被移至 Babel 官方的倉庫中。當時該插件仍使用舊的裝飾器語法,由於新提案還沒有明確。webpack
自那時起,Daniel Ehrenberg、Brian Terlson 以及 Yehuda Katz 就一塊兒成爲了該提案的共同做者,該提案几乎已被徹底重寫。固然並不是一切事情都已肯定,由於至今還沒有出現符合規範的實現方式。git
Babel 7.0.0 爲 @babel/plugin-proposal-decorators
插件引入了新的標識:legacy
選項,其惟一有效值爲 true
。這種突破性變動是必要的,它爲提案從第一階段到當前階段平穩過渡做鋪墊。github
在 Babel 7.1.0 中,咱們引入了對這個新提案的支持,而且當 @babel/plugin-proposal-decorators
插件被使用時,默認啓用。而在 Babel 7.0.0 中若是咱們不設置 legacy: true
選項,默認狀況下就不能使用該語義(至關於 legacy: false
)。web
新提案同時支持使用裝飾器實現私有字段(private fields)和私有方法(private methods)。咱們還沒有在 Babel 中實現此功能(在每一個 class 中使用裝飾器或私有元素),但咱們會很快去出現它。typescript
儘管新提案看起來與舊提案很是類似,但仍是有幾個重要的差別使得它們互不兼容。shell
舊提案容許任何有效的左表達式(字面量,函數,類表達式,new 表達式以及函數調用等)用做裝飾器主體。有效代碼以下所示:npm
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)相關文檔.
並不是全部內容都已肯定:裝飾器是一個很是強大的功能,想要將裝飾器定義爲最好的表現形式,是至關複雜的。
該問題在裝飾器提案中反覆被問到:裝飾器應該放置在關鍵字 export 前仍是關鍵字 export 後?
export @decorator class MyClass {}
// 或者
@decorator
export class MyClass {}
複製代碼
根本問題是 export
關鍵字是不是類聲明的一部分,或者它是不是一個"包裝器"。第一種狀況下,它應該在裝飾器以後,由於裝飾器出如今聲明的起始位置;第二種狀況下,它應該在裝飾器以前,由於裝飾器是類聲明的一部分。
裝飾器引起了一個重要的安全隱患:若是裝飾私有元素,那麼私有名稱(能夠視爲私有元素的 "key")可能會被泄露。有不一樣的安全級別須要考慮:
這些問題須要在解決以前進一步討論,這正是 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 選項進行試用!
掃碼關注咱們的公衆號,咱們會按期推送一下社區相關的文章和動態。