ES6-装饰器以及mobx中@observer为什么写在最后
2020-05-27 本文已影响0人
伪球迷也是球迷啊
在用mobx加antd项目中,发现runinaction后没有重新render,后来查阅文档发现有这样一句话
微信图片_20200527215532.png
@inject('MediaManagementStore')
@observer
@Form.create()
class A extends Com
最开始是这样写的,没有重新render,深入研究见下文。
装饰器ES6文档研究
装饰器目前只是提案阶段,与类相关的语法,主要用来注释或修改类和类的方法,D是一种函数,写成@Log,放在类和类方法的定义前面。
@Log
class Foo {
@desFunc('string')
method = () => {
console.log('msg')
}
}
- 上面Log装饰器和desFunc分别是给类和类方法加的装饰,增加或修改类或类方法。
- 装饰器对类的修改是是在代码编译阶段发生,本质是编译时执行的函数。
- 想给类添加实例属性,可以对target.prototype对象进行操作。
- 多个装饰器会像洋葱模型一样,由外到内进入,由内到外执行。(Mobx的@observer为什么要写在最内层,后面分析)
- 装饰器不能用于函数,因为函数存在提升,而类不会提升,要装饰函数只能用高阶函数的方式去做。
- 通过装饰器可以在一个类中混入另一个对象的方法,mixin模式
- ++以上总结来自于ES6入门++
1. 类的装饰
/**
* 导出装饰器
* @param {*} args
*/
export default function log(...args) {
console.log(args);
if (args.length === 3) {
validateClass(args[0], 'log');
}
if (typeof args[0] !== 'function') {
return function (...argsClass) {
return decorateHandler(argsClass, args[0]);
};
}
return decorateHandler(args);
}
// 等同于
class A {}
A = log(A) || A;
传给装饰器的参数就是目标类,target 就是类本身(而不是其 prototype)
参数.png
2. 类方法(属性)装饰
此时装饰器相当于Object.defineProperty(obj, prop, descriptor) 的语法糖
/**
* 导出装饰器
* @param {*} args
*/
export default function test(...args) {
console.log(args);
/**
* 这里是真正的 decorator
* @target 装饰的属性所述的类的原型,注意,不是实例后的类。如果装饰的是 Car 的某个属性,这个 target 的值就是 Car.prototype
* @name 装饰的属性的 key
* @descriptor 装饰的对象的描述对象
*/
return function (target, key, descriptor) {
console.log(target);
console.log(key);
console.log(descriptor)
}
}
参数.png
这里装饰的是继承自react.Component的类方法
3. 利用装饰器实现一个Log日志输出,展示component日志
- 第一步装饰器导出
/**
* 导出装饰器
* @param {*} args
*/
export default function log(...args) {
console.log(args);
// 装饰类
if (typeof args[0] !== 'function') {
return function (...argsClass) {
console.log(argsClass)
return decorateHandler(argsClass, args[0]);
};
}
return decorateHandler(args);
}
// 判断是不是装饰的类,如果是function直接throw error
const validateClass = (class, decorator) => {
if (typeof class !== 'function') {
throw new Error(`@${decorator} decorator can only be applied to class not function`);
}
};
- 判断是不是react.component
function decorateHandler(args, param = false) {
const target = args[0];
onlyError = param;
// 只能应用在class
validateClass(target, 'log');
// 判断基类
const baseTypeName = Object.getPrototypeOf(target).name;
if (baseTypeName === 'ReactComponent' ) {
return logClass(...args);
}
return logClass(...args);
}
- log class 装饰器
/**
* log class 装饰器
* @param {*} target
*/
function logClass(target) {
const opd = Object.getOwnPropertyDescriptors(target.prototype);
Object.keys(opd).forEach((name) => {
if (typeof opd[name].value === 'function') {
proxyMethod(target, name);
}
});
}
Object.getOwnPropertyDescriptors(target.prototype)查看对象属性的特性,返回一个对象,所有原来的对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象
/**
* 代理方法
* @param {*} target
* @param {*} name
*/
function proxyMethod(target, name) {
const original = target.prototype[name];
const wrapFunction = function (...args) {
const beginTime = Date.now();
let result;
try {
result = original.apply(this, args);
} catch (e) {
logger.error(e);
}
const logObj = {};
if (args.length !== 0) {
logObj.args = args;
}
const timeCost = Date.now() - beginTime;
logObj.time = timeCost;
if (this && this.state !== undefined) {
logObj.state = this.state;
}
if (this && this.props !== undefined) {
logObj.props = this.props;
}
!onlyError && logger.groupObj('@log', `${target.name} ${name}`, logObj);
return result;
};
target.prototype[name] = wrapFunction;
}
logger.groupObj传给window.console.groupCollapsed展示props和state,console可设置占位符,具体可以百度文档。
实际展示效果.png
mobx @observer装饰器为什么写在最里层的问题
首先在node_modules/mobx下找到mobx.modules.js
直接上源码
/**
* Turns an object, array or function into a reactive structure.
* @param v the value which should become observable.
*/
function createObservable(v, arg2, arg3) {
// @observable someProp;
// 第二个参数是字符串,调用这个方法,干嘛的后面再去详细看,现在先找问题
if (typeof arguments[1] === "string" || typeof arguments[1] === "symbol") {
return deepDecorator.apply(null, arguments);
}
// it is an observable already, done
// 第一个参数是不是可观察的对象,是就直接返回这个对象
if (isObservable(v))
return v;
// something that can be converted and mutated?
// 判断第一个参数是什么类型,分别调用不同的方法
var res = isPlainObject(v)
? observable.object(v, arg2, arg3)
: Array.isArray(v)
? observable.array(v, arg2)
: isES6Map(v)
? observable.map(v, arg2)
: isES6Set(v)
? observable.set(v, arg2)
: v;
// this value could be converted to a new observable data structure, return it
if (res !== v)
return res;
// otherwise, just box it
fail(process.env.NODE_ENV !== "production" &&
"The provided value could not be converted into an observable. If you want just create an observable reference to the object use 'observable.box(value)'");
}
var observable = createObservable;
export { observable };
上面写了几句中文注释,那么observable.array 这些方法是哪来的,可以继续看下面的observableFactories这个工厂对象,通过
// 给observable添加方法
Object.keys(observableFactories).forEach(function (name) { return (observable[name] = observableFactories[name]); });
好像看错了,后面把这块拆分到mobx源码解读里。
@observer 在mobx.js中
function observe(thing, propOrCb, cbOrFire, fireImmediately) {
if (typeof cbOrFire === "function")
return observeObservableProperty(thing, propOrCb, cbOrFire, fireImmediately);
else
return observeObservable(thing, propOrCb, cbOrFire);
}
function observeObservable(thing, listener, fireImmediately) {
return getAdministration(thing).observe(listener, fireImmediately);
}
function observeObservableProperty(thing, property, listener, fireImmediately) {
return getAdministration(thing, property).observe(listener, fireImmediately);
}
function getAdministration(thing, property) {
if (!thing)
fail("Expecting some object");
if (property !== undefined)
return getAdministration(getAtom(thing, property));
if (isAtom(thing) || isComputedValue(thing) || isReaction(thing))
return thing;
if (isObservableMap(thing) || isObservableSet(thing))
return thing;
// Initializers run lazily when transpiling to babel, so make sure they are run...
initializeInstance(thing);
if (thing[$mobx])
return thing[$mobx];
fail("Cannot obtain administration from " + thing);
}