Как создать декоратор, который добавляет одну функцию в класс компонента?

Я должен предварить это, сказав, что очень мало понимаю в декораторах es7. По сути, мне нужен декоратор с именем @model, который добавляет функцию к компоненту с именем model. Так, например, я бы назвал это так

@model class FooBar extends Component { }

и тогда класс FooBar теперь будет иметь функцию модели.

Вот что я пробовал:

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 };

Логин.js

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

@model class Login extends PureComponent{}

React выдает сообщение об ошибке:

TypeError: супервыражение должно быть либо нулевым, либо функцией, а не объектом

Я без понятия что это значит. Я ищу некоторую помощь в том, чтобы мой декоратор работал, и бонусом было бы понимание концепции декораторов вообще.


person r3wt    schedule 24.01.2018    source источник
comment
К вашему сведению, это не ES7, это отклоненное предложение по синтаксису, поэтому, насколько я понимаю, оно полагается на устаревшее устаревшее преобразование Babel.   -  person Patrick Roberts    schedule 24.01.2018
comment
@PatrickRoberts, приятно знать Патрика, спасибо за информацию.   -  person r3wt    schedule 24.01.2018


Ответы (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;
}

Это намного лучше для производительности, поскольку заставляет декоратора изменять prototype существующего класса один раз с помощью метода-члена model, а не присоединять ограниченную копию метода model в constructor анонимного расширенного класса каждый раз, когда создается новый экземпляр.

Чтобы уточнить, это означает, что в ответе @dfsq reactModelFactory() вызывается каждый раз, когда создается новый экземпляр, а в этом ответе reactModelFactory() вызывается только один раз, когда декоратор активируется в классе.

Причина, по которой я использовал configurable и writable в свойстве descriptor потому, что именно так синтаксис 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);

person Patrick Roberts    schedule 24.01.2018

Ваш декоратор модели не должен возвращать новую функцию. 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);
      }
    }
  }
}
person dfsq    schedule 24.01.2018