平时我们用decorator来封装一些和原有类或者react组件(高阶组件)本身无关的功能。比如说埋点,路由,以及扩展的功能等,非常好用,在此简单记录下。
decorator可以装饰对象和属性,下面就分开介绍。
准备工作
安装babel转码。
npm install --save-dev babel-cli babel-plugin-transform-decorators-legacy
根目录配置.babelrc
{ "presets": ["env"], "plugins": ["transform-decorators-legacy"] }
package.json:
// npm init 后的package.json { "name": "my-project", "version": "1.0.0", "scripts": { // 写入 "build": "./node_modules/.bin/babel 你的原es6.js -o 转成的es5.js" // 目录写法 // "build": "./node_modules/.bin/babel src -d lib" }, "devDependencies": { "babel-cli": "^6.0.0" } }
babel-cli安装在全局的话,就不需要按照上面在以上script中添加build了。
运行:
npm run build // 全局babel-cli 使用 babel 你的原es6.js > 转成的es5.js
即可生成可运行的es5文件,然后使用node file.js在node里运行,或者引入html中即可。
当然也可以直接在 babel网站 中编辑转码。
decorator 装饰属性
装饰属性,会在属性注册到类prototype之前先执行装饰器,先看源码怎么写。
// 定义一个装饰器函数,里面的target这些参数和Object.defineProperty是对应的 function ready(target, name, descripter) { descripter.writable = false; // 改写writable属性 return descripter; // 注意,返回属性描述对象descriptor } class A() { @ready b() { console.log('f'); } }
再看babel转义成es5的核心代码:
function applyDecorator(target, property, decorators, descriptor, context) { // 定义一个新的描述对象 var desc = {}; // 将target.property 的descriptor挂载在desc上 // 从后往前依次覆盖前面的desc上的属性 desc = decorators.slice().reverse().reduce(function(desc, decorator) { return decorator(target, propery, desc) || desc; }); return desc; } applyDecorator(A.prototype, 'b', [ready], Object.getOwnPropertyDescriptor(A.prototype, 'b'), A.prototype);
经过ready处理后的descriptor 返回到类A(Class)的原型上的属性b上,之后对b方法的重写将会被禁止,这样我们可以针对b方法进行一些限制、拦截和改造性质的操作,包括get、 set方法。
另外,这里的reduce用的非常好。将含有处理函数的数组倒置,,然后使用reduce,每次处理函数返回值作为下一次的第一个属性再次传入,下一个处理函数继续处理返回值,redux库插件处理state就是这种思路。
decorator 装饰对象
装饰对象是大家使用decorator最广泛的场景。
function filter(flag) { return function(target) { // handler flag // 也可以写一些自定义的方法挂载在target上 target.getName = function() { return 'wf' }; } } @filter(true) class A { constructor() { this.name = "my" } getName() { return this.name; } } const a = new A(); console.log(a.getName()); // wf
最终转化成es5,直接传入 A,内部改写A的方法。
A = filter(true)(A);
也就是说,装饰器通过传入对象或者对象及方法名,通过改写他的行为。
改写可以通过Object.defineProperty,也可以通过target.otherName的方式,定义和赋值的区别,defineProperty这种能设置属性的特性,限制扩展等。而直接赋值会受到访问器属性的get``set的影响,也会影响访问器属性。
这样我们就能在上面能访问内部的属性,同时也可以添加许多扩展性的功能,这里不作扩展说明,后续设计模式类的文章会介绍。
这里·额外·说下babel转码后的代码里有 这种括号const a = (m, n, v)运算符。
var a = function(v) { return function(v){ return v + 'v'; } } var m; var b = (m = a('v'), m('wf'), ''); // ''
以上代码最终返回空, ()里会依次从前到后计算,最后返回最后一个值!!! 是不是有点像reduce,不过人家无法传递参数,只能指定固定的表达式。
decorator引入React
react的decorator其实是利用高阶组件来完成的:
const Log = (WrappedCompoent) => class extends React.Component { // 扩展的业务逻辑 // 可访问App的this render() { return (); } } @Log class App extends React.Component { // 业务逻辑 constructor(props) { super(props); // 可以取到this.props.otherProps } }
在不影响App组件的正常情况下,App中还可以获取高级组件中定义的方法和属性,同时高阶组件也能做额外的一些事情,特别方便。