原文地址:https://babeljs.io/blog/2018/...
原文做者:Nicolò Ribaudogit
Babel 7.1.0最終支持新的裝飾器提案,能夠經過@babel/plugin-proposal-decorators
插件使用。github
歷史
裝飾器這個概念三年多前被Yehuda Katz首次提出。TypeScript在版本1.5(2015年)中發佈了對裝飾器的支持以及許多ES6特性。不少主流框架,像Angular和MobX,爲了提升開發體驗也開始使用裝飾器。這些使裝飾器變得很流行,而且給了社區一種很穩定的錯覺。shell
Babel在版本5裏面首次實現了裝飾器,但在版本6的時候移除了,由於提案在不斷的變化。Logan Smyth建立了一個非官方的插件(babel-plugin-transform-decorators-legacy
)來代替Babel5裏面的裝飾器,在第一個Babel7 alpha版本發佈的時候的時候,它被移到了Babel官方的存儲庫。這個插件仍是使用老版本的插件語法,由於還不清楚新的提案會變成什麼樣。npm
從那個時候開始,Daniel Ehrenberg和Brain Terlson和Yehuda Katz一塊兒成爲了提案的做者,提案几乎徹底被重寫了。並不是全部的事情都已經肯定,並且目前也沒有合規實施的方案。安全
Babel7.0.0爲@babel/plugin-proposal-decorators
插件介紹了一個新的標誌:配置項legacy的惟一有效值爲true。爲了從提案的第一階段平滑過渡到當前版本,須要有這種重大的改變。babel
在Babel7.1.0,咱們引入了對這個新提案的支持,而且在使用@babel/plugin-proposal-decorators
插件的時候會默認啓用。若是咱們不在Babel7.0.0裏引入配置項legacy爲true的話,在默認狀況下就不可能使用正確的語義(也就意味着配置項legacy的值爲false)app
新的提案還支持私有字段和方法上的裝飾器。咱們尚未在Babel中實現這個功能(對於每一個類而言,你可使用裝飾器或者私有元素),但很快就會實現的。框架
新的提案的改變點
儘管新的提案看上去跟舊的很類似,但仍是有一些重要的不一樣點。函數
語法測試
舊的提案容許任何有效的左側表達式(文字、函數和類表達式,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() {}, };
因爲跟當前對象的一些表達語法的不兼容性,在提案中被移除了。若是你在你的代碼中使用了對象成員裝飾器,繼續關注由於它們可能會在後續提案中被引入。
在初版提案中,類元素裝飾器接受一個目標類(對象),一個變量,和一個屬性描述符-相似於傳遞給Object.defineProperty
的參數。類裝飾器將目標構造函數做爲惟一的參數。
新的提案的裝飾器更強大一些:元素裝飾器接受一個對象,該對象除了更改屬性操做符以外,還容許更改變量值,位置(static
、prototype
或者own
)以及元素的種類(field
或method
)。他們還能夠建立其餘的屬性並定義運行在類裝飾器裏的函數。
類裝飾器接受一個包含每一個類元素的描述符的對象,從而保證能夠在建立類以前修改它們。
升級
因爲這些不兼容性,不能在現有的裝飾器上使用新的提案:這會讓升級特別慢,由於現有的庫(MobX,Angular等)不能再沒有引入重大改變的狀況下進行升級。爲了解決這個問題,咱們已經發布了一個實用程序包,它將裝飾器包裝在你的代碼裏。運行這個以後,你能夠安全地修改你的Babel配置以使用新的提案。
你可使用下面這行代碼去升級文件:
npx wrap-legacy-decorators src/file-with-decorators.js --decorators-before-export --write
若是你的代碼只在Node中運行,或者你用Webpack或Rollup打包你的代碼,你可使用外部依賴來避免在每一個文件中都注入包裝函數:
npm install --save decorators-compat npx wrap-legacy-decorators src/file-with-decorators.js --decorators-before-export --external-helpers --write
開放問題
並不是全部的事情都已經肯定:裝飾器是一個很是大的功能,並且要以最好的方式定義它們是很是複雜的。
導出類的裝飾器應該放在哪裏
這個問題在裝飾器的提案裏反覆出現:裝飾器應該在export這個關鍵字的前面仍是後面?
export @decorator class MyClass {} // or @decorator export class MyClass {}
根本問題是export關鍵字是不是類聲明的一部分,仍是隻是一個「包裝器」。若是是前一種狀況,它應該放在裝飾器的後面,由於裝飾器出如今聲明的開頭;在第二種狀況下,它應該在裝飾器前面,由於裝飾器是類裝飾器的一部分。
如何讓裝飾器和私有元素安全地互動?
裝飾器引發了重要的安全問題:若是能夠裝飾私有元素,那麼私有名稱(也能夠稱爲私有屬性的變量名)可能會被泄漏。有不一樣的安全級別須要考慮:
1) 裝飾器不該該意外泄漏私有名稱。惡意代碼不該該以任何方式從其餘裝飾器中「竊取」私有名稱。
2) 只有直接應用於私有元素的裝飾器才能被視爲可信任:類裝飾器應該沒法讀寫私有元素?
3) 硬私有(類字段提案的目標之一)意味着私有元素應該只能有類的內部訪問:任何裝飾器是否能夠訪問私有名稱?裝飾器只能裝飾公共元素麼?
這些問題須要進一步討論才能解決,這也是Babel的用武之地。
Babel的做用
隨着What's Happening With the Pipeline(|>) Proposal?這篇文章裏的趨勢,隨着Babel7的發佈,咱們開始利用咱們在JS生態系統中的位置,經過讓開發人員測試和反饋有關提案的不一樣版本的體驗來幫助提案的提出者們。
因爲這個緣由,在@babel/plugin-proposal-decorators更新的同時,咱們也引入了一個新的屬性:decoratorsBeforeExport
,容許用戶同時使用export @decorator class C {}
和 @decorator export default class
。
咱們也將引入一個屬性來自定義私有屬性裝飾器的隱私約束。在TC39人員作出決定以前,這些屬性是必需的,這樣咱們可讓默認行爲成爲最終提案知道的內容。
若是你直接使用咱們的解析器(@babel/parse,之前的babylon),你已經能夠在版本7.0.0裏使用decoratorsBeforeExport屬性:
const ast = babylon.parse(code, { plugins: [ ["decorators", { decoratorsBeforeExport: true }] ] })
用法
Babel用法:
shell版本:
npm install @babel/plugin-proposal-decorators --save-dev
JSON版本:
{ "plugins": ["@babel/plugin-proposal-decorators", {"decoratorsBeforeExport": true }] }
查看@babel/plugin-proposal-decorators文檔瞭解更多屬性。
你的做用
做爲JavaScript的開發人員,你能夠幫助概述該語言的將來。你能夠測試裝飾器的新語法,並向提案的做者們提出反饋意見。咱們須要知道你在現實生活的項目中是怎麼使用它們的。你也能夠經過閱讀問題中的討論和proposal's repository中的筆記中發現爲何要這樣設計。
若是你想要當即嘗試裝飾器,你能夠在咱們的repl裏使用不一樣的預設屬性值。