因为装饰器属于一个在提案中的语法,所以不管是node还是浏览器,现在都没有直接支持这个语法,我们要想使用该语法,就必须要通过babel将它进行一个编译转换,所以我们需要搭建一个babel编译环境。
1、安装babel相关包
npm i @babel/cli @babel/core @babel/plugin-proposal-decorators @babel/preset-env -D
2、在项目根目录下创建.babelrc
{
"presets": [
"@babel/preset-env"
],
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
]
]
}
基础环境搭建好以后,接下来我们就可以尽情的使用装饰器了
类装饰器,顾名思义就是用来装饰整个类的,可以用来修改类的一些行为。
// src/demo01.js
// 类装饰器的简单应用
function log(target) {
console.log('target: ', target);
}
@log
class App {
}
编译,执行
// 使用babel编译,将代码编译输出到dist文件夹
npx babel src/demo01.js -d dist
// 执行编译后的代码
node dist/demo01.js
// 编译后的代码
"use strict";
var _class;
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
// src/demo01.js
// 类装饰器的简单应用
function log(target) {
console.log('target: ', target);
}
var App = log(_class = function App() {
_classCallCheck(this, App);
}) || _class;
这是babel编译后的源代码,其实babel加了一下额外的逻辑,删掉这些逻辑后,装饰器转换后的代码其实是下面这样子的:
function log(target) {
console.log('target: ', target);
}
class App {};
log(App);
执行输出:
target: [Function: App]
可以看到其实类装饰器就是一个函数,接受一个类作为参数,装饰器函数内部的target参数就是被装饰的类本身,我们可以在装饰器函数内部对这个类进行一些修改,比如:添加静态属性,给原型添加函数等等。
带参数的装饰器,需要在外面再套一层接受参数的函数,像下面这样:
// src/demo02.js
function log(msg) {
console.log('msg: ', msg);
return function(target) {
console.log('target: ', target);
target.msg = msg;
}
}
@log('Jameswain')
class App {
}
console.log('App: ', App);
// 编译
npx babel src/demo02.js -d dist
// 执行
node src/demo02.js
为了方便大家理解,我将babel编译后的代码进行了简化,删除了干扰逻辑
// dist/demo02.js
"use strict";
function log(msg) {
console.log('msg: ', msg);
return function _dec (target) {
console.log('target: ', target);
target.msg = msg;
};
}
var _dec = log('Jameswain');
function App() {
}
_dec(App);
console.log('App: ', App);
执行结果:
msg: Jameswain
target: [Function: App]
App: [Function: App] { msg: 'Jameswain' }
我们平时开发中使用的react-redux就有一个connect装饰器,它可以把redux中的变量注入到指定类创建的实例中,下面我们就通过一个例子模拟实现connect的功能:
// src/demo03.js => 模拟实现react-redux的connect功能
// connect装饰器
const connect = (mapStateToProps, mapDispatchToProps) => target => {
const defaultState = {
name: 'Jameswain',
text: 'redux默认信息'
};
// 模拟dispatch函数
const dispatch = payload => console.log('payload: ', payload);
const { props } = target.prototype;
target.prototype.props = { ...props, ...mapStateToProps(defaultState), ...mapDispatchToProps(dispatch) };
}
const mapStateToProps = state => state;
const mapDispatchToProps = dispatch => ({
setUser: () => dispatch({ type: 'SET_USER' })
})
@connect(mapStateToProps, mapDispatchToProps)
class App {
render() {
console.log('渲染函数');
}
}
const app = new App();
console.log('app: ', app);
console.log('app.props: ', app.props);
// 编译
npx babel src/demo03.js
// 执行
node dist/demo03.js
输出结果:
app: App {}
app.props: { name: 'Jameswain', text: 'redux默认信息', setUser: [Function: setUser] }
从输出结果中可以看到,效果跟react-redux的connect装饰器一样,返回值都被注入到App实例中的props属性中,下面我们来看看编译出来的代码长什么样子,老规矩为了方便大家理解,我删除掉babel的干扰代码,只保留核心逻辑:
// dist/demo03.js
"use strict";
// 模拟实现react-redux的connect功能
// connect装饰器
function connect(mapStateToProps, mapDispatchToProps) {
return function (target) {
var defaultState = {
name: 'Jameswain',
text: 'redux默认信息'
};
function dispatch(payload) {
return console.log('payload: ', payload);
};
var props = target.prototype.props;
target.prototype.props = { ...props, ...mapStateToProps(defaultState), ...mapDispatchToProps(dispatch) };
};
};
function mapStateToProps(state) {
return state;
};
function mapDispatchToProps(dispatch) {
return {
setUser: function setUser() {
return dispatch({
type: 'SET_USER'
});
}
};
};
function App() {}
App.prototype.render = function() {
console.log('渲染函数');
}
connect(mapStateToProps, mapDispatchToProps)(App);
var app = new App();
console.log('app: ', app);
console.log('app.props: ', app.props);
对比编译后的代码,可以发现其实装饰器就是一个语法糖而已,实现一模一样,只是调用的方式不一样。
// 装饰器用法
@connect(mapStateToProps, mapDispatchToProps)
class App {}
// 函数式用法
@connect(mapStateToProps, mapDispatchToProps)(class App {})
一个类中可以有多个装饰器,装饰器的执行顺序是:从下往上,从右往左执行。比如下面这个例子:
// src/demo04.js 装饰器的执行顺序
function log(target) {
console.log('log: ', target);
}
function connect(target) {
console.log('connect: ', target);
}
function withRouter(target) {
console.log('withRouter: ', target);
}
@log
@withRouter
@connect
class App {
}
// 编译
npx babel src/demo04.js -d dist
// 执行
node dist/demo04.js
运行结果:
# 从下往上执行
connect: [Function: App]
withRouter: [Function: App]
log: [Function: App]
编译后的代码:
// src/demo04.js 装饰器的执行顺序
"use strict";
function log(target) {
console.log('log: ', target);
}
function connect(target) {
console.log('connect: ', target);
}
function withRouter(target) {
console.log('withRouter: ', target);
}
var _class;
var App = log(_class = withRouter(_class = connect(_class = function App() {
}) || _class) || _class) || _class;
从编译后的代码中可以看出,多个装饰器其实就是一层层的函数嵌套,从里往外执行,但是显然是装饰逻辑更清晰,易读。
来自:https://segmentfault.com/a/1190000040051353
装饰器:装饰器是一种特殊类型的声明,它能够被附加到类声明,方法,属性或参数上,可以修改类的行为。通俗的讲装饰器就是一个方法,可以注入到类、方法、属性参数上来扩展类、属性、方法、参数的功能。
装饰器(Decorator)是ES7的一个语法,是一种与类相关的语法,用来注释或修改类和类的方法。装饰器是一种函数,写成 @ + 函数名。它可以放在类和类方法的定义前面
三个装饰器,都是利用了async/await把异步变成同步的特性实现的。要求被装饰的方法必须写成async/await,用起来十分方便,实现彻底被隐藏在了装饰器内部。前两个都是用在ts环境下class写法的vue里的。
TypeScript 是 JavaScript 语言的扩展,它使用 JavaScript 的运行时和编译时类型检查器。这种组合允许开发人员使用完整的 JavaScript 生态系统和语言功能
装饰器模式是一种经典的设计模式,它可以在不修改被装饰者(如某个函数、某个类等)源码的前提下,为被装饰者增加 / 移除某些功能(收集用户定义的类/函数的信息,例如用于生成路由表,实现依赖注入等等
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!