JS декоратор, достаточно одной статьи

Python внешний интерфейс JavaScript React.js

Для получения дополнительных статей, пожалуйста, посетитеGithub blogПроверить

В ES6 были добавлены связанные определения и операции над объектами класса (такими как класс и расширения), что делает его не таким элегантным, когда мы разделяем или расширяем некоторые методы или поведения между несколькими разными классами. В настоящее время нам нужен более элегантный способ, который поможет нам выполнить эти задачи.

что такое декоратор

Декораторы для Python

В шаблонах объектно-ориентированного проектирования (ООП) декораторы известны как шаблоны оформления. Режим украшения ООП должен быть реализован через наследование и композицию, и Python поддерживает декораторы непосредственно на уровне синтаксиса в дополнение к декораторам, которые могут поддерживать ООП.

Если вы знакомы с python, вы должны быть знакомы с ним. Итак, давайте посмотрим, как выглядит декоратор в Python:

def decorator(f):
    print "my decorator"
    return f
@decorator
def myfunc():
    print "my function"
myfunc()
# my decorator
# my function

Здесь @decorator — это то, что мы называем декоратором. В приведенном выше коде мы используем декоратор для печати строки текста перед выполнением нашего целевого метода и не вносим никаких изменений в исходный метод. Код в основном эквивалентен:

def decorator(f):
    def wrapper():
        print "my decorator"
        return f()
    return wrapper
def myfunc():
    print "my function"
myfunc = decorator(myfuc)

Нетрудно заметить из кода, что декоратор-декоратор получает параметр, являющийся целевым методом, который мы декорируем, после обработки расширенного содержимого возвращает метод для последующего вызова, а также теряет доступ к исходному объекту метода. Когда мы применяем украшение к определенному методу, мы фактически меняем ссылку на вход украшенного метода, чтобы она указывала на точку входа метода, возвращаемого декоратором, чтобы реализовать наше расширение и модификацию исходной функции.

Декораторы для ES7

Декоратор в ES7 также заимствует этот синтаксический сахар, но опирается на ES5.Object.definePropertyметод .

Object.defineProperty

Object.defineProperty()Метод определяет новое свойство непосредственно в объекте или изменяет существующее свойство объекта и возвращает этот объект.

Этот метод позволяет точно добавлять или изменять свойства объекта. Обычные свойства, добавляемые присваиванием, создают свойства, отображаемые при перечислении свойств (методы for...in или Object.keys), значения которых можно изменить или удалить. Этот подход позволяет изменить эти дополнительные детали по умолчанию. По умолчанию используйтеObject.defineProperty()Добавленные значения свойств являются неизменяемыми.

грамматика

Object.defineProperty(obj, prop, descriptor)
  • obj: Объект, для которого определяется свойство.
  • prop: имя свойства, которое необходимо определить или изменить.
  • descriptor: Дескриптор свойства, который необходимо определить или изменить.
  • Возвращаемое значение: объект, переданный функции.

В ES6 из-за специфики типа Symbol использование значения типа Symbol в качестве ключа объекта отличается от обычного определения или модификации, иObject.defineProperty— это один из способов определения свойства, ключ которого — Symbol.

дескриптор свойства

В настоящее время в объектах существуют две основные формы дескрипторов свойств:дескриптор данныха такжедескриптор доступа.

  • дескриптор данных— это свойство со значением, которое может быть доступно или недоступно для записи.
  • дескриптор доступа— это свойство, описываемое парой функций получения-установки.

Дескриптор должен быть одной из этих двух форм, он не может быть обеими одновременно.

И дескрипторы данных, и дескрипторы доступа имеют следующие необязательные ключи:

configurable

Если и только если параметр configurable свойства имеет значение true, дескриптор свойства может быть изменен, а свойство может быть удалено из соответствующего объекта. По умолчанию ложно.

enumerable

enumerable определяет, могут ли свойства объекта быть перечислены в циклах for...in и Object.keys() .

Свойство может появиться в перечисляемом свойстве объекта тогда и только тогда, когда перечисляемое свойство имеет значение true. По умолчанию ложно. Дескриптор данных также имеет следующие необязательные ключи:

value

Значение, соответствующее этому свойству. Может быть любым допустимым значением JavaScript (число, объект, функция и т. д.). По умолчанию не определено.

writable

Значение может быть изменено оператором присваивания тогда и только тогда, когда свойство имеет значение true. По умолчанию ложно.

Дескриптор доступа также имеет следующие необязательные ключи:

get

Метод, предоставляющий метод получения для свойства, или undefined, если метод получения отсутствует. Возвращаемое значение этого метода используется в качестве значения свойства. По умолчанию не определено.

set

Метод, предоставляющий установщик для свойства, или undefined, если установщика нет. Метод примет уникальный параметр и присвоит свойству новое значение этого параметра. По умолчанию не определено.

Если дескриптор не содержит ключевых слов value, writable, get и set, он считается дескриптором данных. Если дескриптор имеет ключевые слова (значение или доступное для записи) и (получение или установка), будет возбуждено исключение.

Применение

украшение класса

@testable
class MyTestableClass {
  // ...
}

function testable(target) {
  target.isTestable = true;
}

MyTestableClass.isTestable // true

В приведенном выше коде@testableявляется декоратором. Он изменяет поведение класса MyTestableClass, добавляя к нему статическое свойство isTestable. Целевым параметром тестируемой функции является сам класс MyTestableClass.

В основном поведение декоратора выглядит следующим образом.

@decorator
class A {}

// 等同于

class A {}
A = decorator(A) || A;

То есть,Декоратор — это функция, которая что-то делает с классом. Первый параметр функции декоратора — это целевой класс, который нужно декорировать..

Если вы чувствуете, что одного параметра недостаточно, вы можете инкапсулировать еще один слой функций вне декоратора.

function testable(isTestable) {
  return function(target) {
    target.isTestable = isTestable;
  }
}

@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true

@testable(false)
class MyClass {}
MyClass.isTestable // false

В приведенном выше коде проверяемый декоратор может принимать параметры, что эквивалентно изменению поведения декоратора.

Уведомление,Декораторы изменяют поведение класса во время компиляции, а не во время выполнения.. Это означает, что декоратор может запускать код во время компиляции. То есть,Декораторы — это, по сути, функции, которые выполняются во время компиляции..

В предыдущем примере показано добавление статического свойства к классу.Если вы хотите добавить свойства экземпляра, вы можете работать с объектом-прототипом целевого класса.

Ниже приведен еще один пример.

// mixins.js
export function mixins(...list) {
  return function (target) {
    Object.assign(target.prototype, ...list)
  }
}

// main.js
import { mixins } from './mixins'

const Foo = {
  foo() { console.log('foo') }
};

@mixins(Foo)
class MyClass {}

let obj = new MyClass();
obj.foo() // 'foo'

Приведенный выше код добавляет метод объекта Foo к экземпляру MyClass через примеси декоратора.

метод украшения

Декораторы могут украшать не только классы, но и свойства классов.

class Person {
  @readonly
  name() { return `${this.first} ${this.last}` }
}

В приведенном выше коде декоратор только для чтения используется для оформления имени метода «класса».

Функция декоратора только для чтения может принимать всего три параметра.

function readonly(target, name, descriptor){
  // descriptor对象原来的值如下
  // {
  //   value: specifiedFunction,
  //   enumerable: false,
  //   configurable: true,
  //   writable: true
  // };
  descriptor.writable = false;
  return descriptor;
}

readonly(Person.prototype, 'name', descriptor);
// 类似于
Object.defineProperty(Person.prototype, 'name', descriptor);
  • Первый параметр декоратораобъект-прототип класса, приведенный выше пример — это Person.prototype, первоначальное намерение декоратора — «украсить» экземпляр класса, но экземпляр еще не сгенерирован, поэтому мы можем только украсить прототип (Это отличается от оформления класса, и в этом случае целевой параметр относится к самому классу.);
  • Второй параметрНазвание объекта для украшения
  • Третий параметробъект описания свойства

Кроме того, приведенный выше код показывает, что装饰器(readonly) изменит свойство描述对象(descriptor)Затем объект измененного описания затем используется для определения атрибутов.

украшение метода функции

Декораторы можно использовать только в классах и методах классов, а не в функциях из-за подъема функций..

С другой стороны, если вам нужно декорировать функцию, вы можете выполнить ее непосредственно в форме функции более высокого порядка.

function doSomething(name) {
  console.log('Hello, ' + name);
}

function loggingDecorator(wrapped) {
  return function() {
    console.log('Starting');
    const result = wrapped.apply(this, arguments);
    console.log('Finished');
    return result;
  }
}

const wrapped = loggingDecorator(doSomething);

core-decorators.js

core-decorators.js— это сторонний модуль, предоставляющий несколько общих декораторов, с помощью которых декораторы можно лучше понять.

@autobind

Декоратор autobind связывает объект this в методе с исходным объектом.

@readonly

Декоратор только для чтения делает свойство или метод недоступным для записи.

@override

Декоратор переопределения проверяет, правильно ли метод подкласса переопределяет одноименный метод родительского класса, и если это не так, будет сообщено об ошибке.

import { override } from 'core-decorators';

class Parent {
  speak(first, second) {}
}

class Child extends Parent {
  @override
  speak() {}
  // SyntaxError: Child#speak() does not properly override Parent#speak(first, second)
}

// or

class Child extends Parent {
  @override
  speaks() {}
  // SyntaxError: No descriptor matching Child#speaks() was found on the prototype chain.
  //
  //   Did you mean "speak"?
}

@deprecate (псевдоним @deprecated)

Устаревший или устаревший декоратор выводит на консоль предупреждение о том, что метод будет устаревшим.

import { deprecate } from 'core-decorators';

class Person {
  @deprecate
  facepalm() {}

  @deprecate('We stopped facepalming')
  facepalmHard() {}

  @deprecate('We stopped facepalming', { url: 'http://knowyourmeme.com/memes/facepalm' })
  facepalmHarder() {}
}

let person = new Person();

person.facepalm();
// DEPRECATION Person#facepalm: This function will be removed in future versions.

person.facepalmHard();
// DEPRECATION Person#facepalmHard: We stopped facepalming

person.facepalmHarder();
// DEPRECATION Person#facepalmHarder: We stopped facepalming
//
//     See http://knowyourmeme.com/memes/facepalm for more details.
//

@suppressWarnings

Декоратор submitWarnings подавляет вызовы console.warn(), вызванные устаревшим декоратором. Однако исключением являются вызовы, сделанные асинхронным кодом.

сцены, которые будут использоваться

Декораторы имеют эффект аннотаций

@testable
class Person {
  @readonly
  @nonenumerable
  name() { return `${this.first} ${this.last}` }
}

Из приведенного выше кода видно, что класс Person можно тестировать, а метод name доступен только для чтения и не является перечисляемым.

Реагировать на подключение

В реальной разработке, когда React используется в сочетании с библиотекой Redux, часто бывает необходимо написать следующее.

class MyReactComponent extends React.Component {}

export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);

С помощью декораторов приведенный выше код можно переписать. Украсить

@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}

Условно говоря, последний способ написания кажется более понятным.

Оповещения о новых функциях или разрешения

Когда меню нажат, событие будет перехвачено. Если в меню появится новое обновление функций, появится всплывающее окно.

/**
 * @description 在点击时,如果有新功能提醒,则弹窗显示
 * @param code 新功能的code
 * @returns {function(*, *, *)}
 */
 const checkRecommandFunc = (code) => (target, property, descriptor) => {
    let desF = descriptor.value; 
    descriptor.value = function (...args) {
      let recommandFuncModalData = SYSTEM.recommandFuncCodeMap[code];

      if (recommandFuncModalData && recommandFuncModalData.id) {
        setTimeout(() => {
          this.props.dispatch({type: 'global/setRecommandFuncModalData', recommandFuncModalData});
        }, 1000);
      }
      desF.apply(this, args);
    };
    return descriptor;
  };

loading

В проекте React нам может понадобиться анимация загрузки на странице при запросе данных из фона. На этом этапе вы можете использовать декораторы для элегантной реализации функций.

@autobind
@loadingWrap(true)
async handleSelect(params) {
  await this.props.dispatch({
    type: 'product_list/setQuerypParams',
    querypParams: params
  });
}

Функция loadingWrap выглядит следующим образом:

export function loadingWrap(needHide) {

  const defaultLoading = (
    <div className="toast-loading">
      <Loading className="loading-icon"/>
      <div>加载中...</div>
    </div>
  );

  return function (target, property, descriptor) {
    const raw = descriptor.value;
    
    descriptor.value = function (...args) {
      Toast.info(text || defaultLoading, 0, null, true);
      const res = raw.apply(this, args);
      
      if (needHide) {
        if (get('finally')(res)) {
          res.finally(() => {
            Toast.hide();
          });
        } else {
          Toast.hide();
        }
      }
    };
    return descriptor;
  };
}

Вопрос: Здесь можно подумать, если мы не хотим, чтобы загрузка отображалась каждый раз, когда мы запрашиваем данные, а требуем, чтобы загрузка отображалась только тогда, когда время фонового запроса больше 300 мс, что здесь нужно изменить?

Ссылаться на