原文地址: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里使用不一样的预设属性值。