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,因此可以这样改,来根据需求进行埋点
上一篇: 基于MySQL INNODB的优化技巧