开发者问题收集

如何创建一个向组件类添加单个功能的装饰器?

2018-01-24
495

首先,我应该说我对 es7 装饰器了解甚少。基本上,我想要的是一个名为 @model 的装饰器,它将一个函数添加到名为 model 的组件。例如,我会这样调用它

@model class FooBar extends Component { }

然后 FooBar 类现在将具有 model 函数。

这是我尝试过的:

Model.js

export default function reactModelFactory( ctx ){

    return (key)=>{
        return {
            onChange: (e)=>ctx.setState({[key]:e.target.value}),
            value: ctx.state[key],
            name: key
        };
    };

};

function modelDecorator() {
    return function(ctx){
        return class extends ctx{
            constructor(...args){
                super(...args);
                this.model = reactModelFactory(this);
            }
        }
    }
}

export { modelDecorator as model };

Login.js

import React,{PureComponent} from 'react';
import {model} from './Model';

@model class Login extends PureComponent{}

React 抛出错误消息:

TypeError: Super expression must either be null or a function, not object

我不知道这是什么意思。我正在寻求一些帮助以使我的装饰器正常工作,并且额外的好处是完全理解装饰器的概念。

2个回答

为了补充@dfsq的答案(我假设它做了你想要的),你可以在接口性能方面更进一步,通过将 model() 添加到 prototype 而不是每个实例,如下所示:

export default function reactModelFactory() {
  return function model (key) {
    return {
      onChange: (e) => this.setState({ [key]: e.target.value }),
      value: this.state[key],
      name: key
    };
  };
};

function modelDecorator(Class) {
  Object.defineProperty(Class.prototype, 'model', {
    value: reactModelFactory(),
    configurable: true,
    writable: true
  });

  return Class;
}

这对性能来说要好得多,因为它会导致装饰器使用 model 成员方法一次修改现有类的 prototype ,而不是每次构造新实例时在匿名扩展类的 constructor 中附加 model 方法的作用域副本。

澄清一下,这意味着在@dfsq的答案中,每次构造新实例时都会调用 reactModelFactory() ,而在这个答案中, reactModelFactory() 仅在激活装饰器时调用一次在类上。

我在 属性描述符 中使用 configurablewritable 的原因是因为这就是 class { } 语法在 prototype 上本机定义成员方法的方式:

class Dummy {
  dummy () {}
}

let {
  configurable,
  writable,
  enumerable
} = Object.getOwnPropertyDescriptor(Dummy.prototype, 'dummy');

console.log('configurable', configurable);
console.log('enumerable', enumerable);
console.log('writable', writable);
Patrick Roberts
2018-01-24

您的模型装饰器不应返回新函数。 ctx 将传递给 modelDecorator 本身。因此,您实际上只需要从中返回新扩展的类:

function modelDecorator(ctx) {
  return class extends ctx {
    constructor(...args) {
      super(...args);
      this.model = reactModelFactory(this);
    }
  }
}

请注意,如果您的装饰器应该像这样使用(Angular 样式装饰器),您尝试的语法将起作用:

@model({ modelName: 'user' })
class Login extends PureComponent {}

然后,您需要额外的闭包来将传递的参数保留到您的装饰器中:

function modelDecorator({ modelName }) {
  return (ctx) => {
    console.log('model name', modelName)
    return class extends ctx {
      constructor(...args) {
        super(...args);
        this.model = reactModelFactory(this);
      }
    }
  }
}
dfsq
2018-01-24