Decorator - 利用装饰器武装前端代码
历史
以前做后端时,接触过一点Spring,也是第一次了解DI、IOC等概念,面向切面编程,对于面向对象编程还不怎么熟练的情况下,整个人慌的一批,它的日志记录、数据库配置等都非常方便,不回侵入到业务代码中,后来转战前端,就没怎么关注了.....

JS引入DI编程概念
学习 redux 时,看到语法里面有 @ 符号,卧槽,后端已经侵入到前端啦,不知不觉中,前端已经这么NB了,再也不是写写页面,用个框架,绑定个事件啦,已经把后端的一些经典设计思想融入进来了
对于前端开发而言,如果有一种方式,能够将一些非业务代码,甚至抽象的东西,无侵入的方式挂载到业务代码上,那么对于个人而言,这真是一种解放,太帅了......
装饰器初探
1.给方法记录一下log
@log
class Numberic {
add(...nums) {
return nums.reduce((p, n) => (p + n), 0)
}
} function log(target) {
// Numberic
const desc = Object.getOwnPropertyDescriptors(target.prototype)
/**
* desc
add:
configurable: true - 可配置
enumerable: false - 可枚举
value: ƒ ()
writable: true - 可改写
__proto__: Object constructor:
configurable: true
enumerable: false
value: ƒ Numberic()
writable: true
__proto__: Object
*/ for (const key of Object.keys(desc)) {
if (key === 'constructor') {
continue
} const func = desc[key].value if ('function' === typeof func) {
Object.defineProperty(target.prototype, key, {
value(...args) {
console.log('before ' + key)
const ret = func.apply(this, args)
console.log('after ' + key)
return ret
}
})
}
}
} new Numberic().add(2)
// before add
// 2
// after add
2.给属性添加readonly校验
@log
class Numberic {
@readonly PI = 3.1415126 add(...nums) {
return nums.reduce((p, n) => (p + n), 0)
}
} function readonly(target, key, descriptor) {
descriptor.writable = false
} new Numberic().PI = 100
// 报错
3.给一个表单提交进行校验
var validateRules = {
expectNumber(value) {
return Object.prototype.toString.call(value) === '[object Number]'
},
maxLength(value) {
return value <= 30
}
}
function validate(value) {
return Object.keys(validateRules).every(key => validateRules[key](value))
}
function enableValidate(target, key, descriptor) {
const fn = descriptor.value
if (typeof fn === 'function') {
descriptor.value = function(value) {
return validate(value)
? fn.apply(this, [value])
: console.error('Form validate failed!')
}
}
}
class Form {
@enableValidate
send(value) {
console.log('This is send action', value)
}
}
let form = new Form()
form.send(44) // Form validate failed!
form.send('12') // Form validate failed!
form.send(12) // This is send action 12
应用React与mobx
import React, { Component } from 'react'
import { render } from 'react-dom'
import { observable, action } from 'mobx'
import { observer } from 'mobx-react'
import { Log, Required, TrackInOut } from './decorator.js'
// store
@Log
class User {
@observable name = ''
@observable password = ''
@action setName = val => {
this.name = val
}
@action setPwd = val => {
this.password = val
}
@action login = (info) => {
console.log('ready to login', info.name, info.password)
}
}
const userStore = new User()
@observer
class Login extends Component {
constructor(props){
super(props)
console.log('原始组件的constructor')
}
@Required(['name', 'password'])
login(info) {
this.props.store.login(info)
}
componentDidMount() {
console.log('原始组件的cmd')
}
render() {
let { name, password, setName, setPwd } = this.props.store
return (
<div className="login-panel">
<input type="text" value={name} onChange={e => setName(e.target.value)}/>
<input type="password" value={password} onChange={e => setPwd(e.target.value)}/><br/>
<button onClick={() => this.login({ name, password })}>登录</button>
</div>
)
}
}
render(<Login store={userStore} />, document.getElementById('root'))
import _ from 'lodash'
import React from 'react'
// 获取方法参数的名称列表
const getArgumentsList = func => {
var funcString = func.toString();
var regExp =/function\s*\w*\(([\s\S]*?)\)/;
if(regExp.test(funcString)){
var argList = RegExp.$1.split(',');
return argList.map(function(arg){
return arg.replace(/\s/g,'');
});
}else{
return []
}
}
// 记录日志
export const Log = target => {
const desc = Object.getOwnPropertyDescriptors(target.prototype)
for (const key of Object.keys(desc)) {
if (key === 'constructor') {
continue
}
const func = desc[key].value
if ('function' === typeof func) {
Object.defineProperty(target.prototype, key, {
value(...args) {
console.log(`before ${key}`)
const ret = func.apply(this, args)
console.log(`after ${key}`)
return ret
}
})
}
}
}
// 只读
export const Readonly = (target, key, descriptor) => {
descriptor.writable = false
}
// 必传参数
export const Required = checkArr => {
return (target, key, descriptor) => {
const fn = descriptor.value
// console.log(target, key, descriptor)
if (typeof fn === 'function') {
descriptor.value = function(args) {
console.log('required')
if (_.isPlainObject(args)) {
if (checkArr && checkArr.length > 0) {
for (let a of checkArr) {
if (!args[a]) {
throw new Error(`[required] params ${a} of ${key} is undefined or null!`)
}
}
}
} else if (_.isArray(args)) {
if (args.length == 0) {
throw new Error(`[required] params ${getArgumentsList(fn)[0]} of ${key} length is 0!`)
}
} else {
if (_.isEmpty(args)) {
throw new Error(`[required] params ${getArgumentsList(fn)[0]} of ${key} is undefined!`)
}
}
fn.apply(this, [args])
}
}
// console.log(target)
// console.log(key)
// console.log(descriptor)
// console.log(checkArr)
}
}
直接应用在mobx上
import React, { Component } from 'react'
import { render } from 'react-dom'
import { observable, action, computed } from 'mobx'
import { observer } from 'mobx-react'
//custom
import { Log, Required, Track } from './decorator.js'
// store
@Log
class User {
@observable name = ''
@observable password = ''
@action setName = val => {
this.name = val
}
@action setPwd = val => {
this.password = val
}
@Required(['name', 'password'])
@Track({ evt: '1', data: 'test', execute: 'after' })
@action
login(info) {
// login 方法如果想要使用Required,则不能使用箭头函数
console.log('login', info.name, info.password)
}
}
const userStore = new User()
@observer
class Login extends Component {
render() {
let { name, password, setName, setPwd } = this.props.store
return (
<div className="login-panel">
<span style={{display:'inline-block', width: 80}}>用户名:</span><input type="text" value={name} onChange={e => setName(e.target.value)}/><br/>
<span style={{display:'inline-block', width: 80}}>密码:</span><input type="password" value={password} onChange={e => setPwd(e.target.value)}/><br/>
<button onClick={() => this.props.store.login({ name, password })}>登录</button>
</div>
)
}
}
render(<Login store={userStore} />, document.getElementById('root'))
无侵入式埋点
最近在做系统的埋点,很多地方要加入埋点,尤其是在一些事件上,如果按照以前的思路,就得将大量的埋点代码侵入到业务代码上,维护上就有点费劲了,因此联想到ES7的decorate 装饰器,可以IOC的方式进行编程,因此,做了一点东西,希望可以给大家带来一点启发





上面的装饰器可以挂载到 function、react的方法、mobx-stroe的action上,但如果有一个需求是这样的,react中,想在进入页面时进行埋点,上面的方法就不太适用了,因为在一个组件上挂载装饰器,它能获取到的上下文对象只是这个组件,既然能获取到这个组件,那么不妨HOC一下,高阶组件一把


发现 高阶组件的constructor 优先与原始组件的 constructor,同时componentDidMount反而晚于原始组件的componentDidMount,因此可以这样改,来根据需求进行埋点
Decorator - 利用装饰器武装前端代码的更多相关文章
- flask中的endpoint、自定义转化器、与djnago中session区别、利用装饰器实现登录认证
flask路由中的endpoint 与自定义转化器 ''' endpoint主要用于 反向解析, 例如:login函数中配的路由是/login,其中endpoint='lg' 则在其他函数,可以用 u ...
- python3 第二十二章 - 函数式编程之Decorator(装饰器)
前面我们说了,在python中,一切皆对象.函数也是一个对象,而且函数对象可以被赋值给变量,通过变量也能调用该函数.如: def sayHello(name): print(name + ' hell ...
- tornado利用装饰器记录每个http请求
python利用装饰器记录每个http请求 设置装饰器 from functools import wraps from datetime import datetime ""&q ...
- 简单理解 ES7 Decorator(装饰器)
如何使用ES7 Decorator给你的游戏人物开挂? // 预告: 本文有点小难度,对js不太熟的人可能比较懵逼 // 本文的目的是让你们知其然 // ======================= ...
- Decorator模式 装饰器模式
Android 使用了装饰器模式 1. 概述 若你从事过面向对象开发,实现给一个类或对象增加行为,使用继承机制,这是所有面向对象语言的一个基本特性.如果已经存在的一个类缺少某些方法,或者须要给方法添加 ...
- 装饰器,栈 ,asyncio 代码
装饰器目的: 不改变原来代码的基础上. 给函数添加新功能动态代理. 拦截器 通用装饰器的写法def wrapper(fn): def inner(*args, **kwargs): '''之前''' ...
- $如何用Python装饰器实现一个代码计时器?
有时候我们很希望看到程序中某个函数或某个代码段的耗时情况,那么该如何办呢?本文用两种方式实现了代码计时器的功能,第一种方式是采用装饰器来实现,第二种方式采用上下文管理器实现. 其实计算代码的运行时间, ...
- python cookbook第三版学习笔记二十一:利用装饰器强制函数上的类型检查
在演示实际代码前,先说明我们的目标:能对函数参数类型进行断言,类似下面这样: @typeassert(int, int) ... def add(x, y): ... return x + y ...
- java 装饰器模式实现代码
目录 1.实现装饰器模式 1.1.公共接口 1.2.接口实现 1.3.装饰器 1.4.装饰构件 1.5.测试装饰器 上图展示的是io流中的一个装饰者模式的代码结构 1.实现装饰器模式 汽车厂生产汽车实 ...
随机推荐
- Under ubuntu 12.04,install sublime text 2
Sublime Text is an awesome text editor. If you’ve never heard of it, you should check it out right n ...
- 今天犯的一个错误,导致method GET must not have a request body
事件经过: 1.在本地机器运行完全正常的程序,手动人工发包到测试环境上,后台日志频频报method GET must not have a request body. 2.使用postman发送pos ...
- Hbase项目(完整版)
涉及概念梳理:命名空间 4.1.1.命名空间的结构 1) Table:表,所有的表都是命名空间的成员,即表必属于某个命名空间,如果没有指定,则在default默认的命名空间中. 2) RegionSe ...
- HD-ACM算法专攻系列(6)——Big Number
题目描述: 源码: #include"iostream" #include"cmath" using namespace std; #define PI 3.1 ...
- 【原创】VSFTP: Login failure: 530 Login incorrect的解决办法
1.修改/etc/vsftpd/ftpusers和/etc/vsftpd/user_list中关于root的行,注释掉即可: 2.关闭SELinux:如果不想关闭的话,可以打开home项的布林值:se ...
- python中index、slice与slice assignment用法
python中index.slice与slice assignment用法 一.index与slice的定义: index用于枚举list中的元素(Indexes enumerate the elem ...
- js表格隔行换色和hover效果
<!--js效果--> <script src="js/jquery.min.js" language="javascript">< ...
- js 将数组中的每一项安装奇偶重新组合成一个数组对象
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- 是否可以从一个static方法内部发出对非static方法的调用
不可以.因为非static方法是要与对象关联在一起的,必须创建一个对象后,才可以在该对象上进行方 法调用,而static方法调用时不需要创建对象,可以直接调用.也就是说,当一个static方法被调用时 ...
- Java将WKT格式的Geomotry转换成GeoJSON
一.Meven添加依赖 <!-- 引入json处理包 --> <dependency> <groupId>com.alibaba</groupId> & ...