ホームページ > ウェブフロントエンド > jsチュートリアル > es7のデコレータを詳しく解説(例付き)

es7のデコレータを詳しく解説(例付き)

不言
リリース: 2018-10-26 15:32:12
転載
4396 人が閲覧しました

この記事では、es7 の Decorator について詳しく説明します (例を示します)。必要な方は参考にしていただければ幸いです。

1. Decorator の基礎知識

多くのフレームワークやライブラリ、特に React、Redux、mobx で見られます。

Decorator は、クラスの動作を変更するために使用される関数です。この抽象的な概念はよく理解できないので、コードを見てより実践的に説明することをお勧めします。

//定义一个函数,也就是定义一个Decorator,target参数就是传进来的Class。
//这里是为类添加了一个静态属性
function addAge(target) {
  target.age = 2;
}

//在Decorator后面跟着Class,Decorator是函数的话,怎么不是addAge(MyGeekjcProject)这样写呢?
//我只能这样理解:因为语法就这样,只要Decorator后面是Class,默认就已经把Class当成参数隐形传进Decorator了(就是所谓的语法糖)。
@addAge
class MyGeekjcProject {}

console.log(MyGeekjcProject.age) // 2
ログイン後にコピー

1.1 パラメータを渡してクラスを変更する

しかし、上記のメソッドが 1 つのクラスのみをパラメータとして渡し、十分な柔軟性がない場合はどうすればよいでしょうか。
最終結果が Decorator である限り、外側の層に関数を設定できます。設定する関数の数や渡すパラメーターの数は関係ありません。

function addAge(age) {
  return function(target) {
    target.age = age;
  }
}

//注意这里,隐形传入了Class,语法类似于addAge(2)(MyGeekjcProject)
@testable(2)
class MyGeekjcProject {}
MyGeekjcProject.age // 2

@addAge(3)
class MyGeekjcProject {}
MyGeekjcProject.age // 3
ログイン後にコピー

1.2 クラスの属性を変更する

以下は変更されたクラスのプロトタイプ オブジェクトです

function description(target) {
  target.prototype.url = 'https://www.geekjc.com';
}

@description
class MyGeekjcProject {}

let geekjc = new MyGeekjcProject();
geekjc.url // https://www.geekjc.com
ログイン後にコピー

修飾子はクラスを変更できるという概念は大まかに理解できます。

//假如修饰类的属性则传入三个参数,对应Object.defineProperty()里三个参数,具体不细说
//target为目标对象,对应为Class的实例
//name为所要修饰的属性名,这里就是修饰器紧跟其后的name属性
//descriptor为该属性的描述对象
//这里的Decorator作用是使name属性不可写,并返回修改后的descriptor
function readonly(target, name, descriptor){
  descriptor.writable = false;
  return descriptor;
}

class Person {
  @readonly
  name() { return `${this.first} ${this.last}` }
}
ログイン後にコピー

Object.defineProperty() については、記事を読んで Object.defineProperty を学習してください。

もう一度複雑な例を見てみましょう。

//定义一个Class并在其add上使用了修饰器
class Math {
  @log
  add(a, b) {
    return a + b;
  }
}

//定义一个修饰器
function log(target, name, descriptor) {
  //这里是缓存旧的方法,也就是上面那个add()原始方法
  var oldValue = descriptor.value;

  //这里修改了方法,使其作用变成一个打印函数
  //最后依旧返回旧的方法,真是巧妙
  descriptor.value = function() {
    console.log(`Calling "${name}" with`, arguments);
    return oldValue.apply(null, arguments);
  };

  return descriptor;
}

const math = new Math();
math.add(2, 4);
ログイン後にコピー
上記のコードを読むと、デコレータが何に使用されるかがわかります。はい、修飾子はクラスの動作を変更するだけでなく、クラスの属性も変更できます。

2. React の高次コンポーネント

より一般的には、高次コンポーネントは、React コンポーネントをラップ (ラップ) することによって渡され、一連の最後に、他のコンポーネントを呼び出すために比較的強化された React コンポーネントが返されます。

3. 上位コンポーネントを実装する

React コンポーネントを受け取り、それをラップして を返す、最も単純な上位コンポーネント (関数) を実装しましょう。

export default function withDescription(WrappedComponent) {
  return class HOC extends Component {
    render() {
      return <div>
        <div>
          极客教程(https://www.geekjc.com)致力于推广各种编程语言技术,也为了未来数字化世界,让人更容易找到操作数字化的方式,为了未来而生的编程学习平台,
          大部分资源是完全免费的,并且会根据当前互联网的变化实时更新本站内容。
        </div>
        <wrappedcomponent></wrappedcomponent>
      </div>
    }
  }
}
ログイン後にコピー
他のコンポーネントでは、この上位コンポーネントを参照して強化します。

@withDescription
export default class Geekjc extends Component {
  render() {
    return (
      <div>
        我是一个普通组件
      </div>
    );
  }
}
ログイン後にコピー
ここでは、書き方の優雅さを向上させるために ES7 のデコレータを使用しています (最初のセクションのデコレータを参照) が、実際には単なる構文上の糖衣です。次のような書き方も可能です。

const EnhanceDemo = withDescription(Geekjc);
ログイン後にコピー
次に、React コンポーネント ツリーでどのような変更が行われたかを観察します。図に示すように、Geekjc コンポーネントが HOC コンポーネントによってラップされていることがわかります。これは、高い期待と一致しています。 -order コンポーネント。つまり、コンポーネントはタマネギのようにレイヤーごとにラップされます。

es7のデコレータを詳しく解説(例付き)

しかし、それに伴う問題は、この上位コンポーネントが複数回使用されると、デバッグ時に次のように表示されることです。

HOC が大量にあるため、この時点で少し最適化を行う必要があります。つまり、上位コンポーネントがラップされた後、元の名前を保持する必要があります。

上記の上位コンポーネントのコードを書き換えて、

getDisplayName 関数と静的属性 displayName を追加して、 DOM Tree を観察します。

function getDisplayName(component) {
  return component.displayName || component.name || 'Component';
}
export default function (WrappedComponent) {
  return class HOC extends Component {
    static displayName = `HOC(${getDisplayName(WrappedComponent)})`
    render() {
      return <div>
        <div>
          极客教程(https://www.geekjc.com)致力于推广各种编程语言技术,也为了未来数字化世界,让人更容易找到操作数字化的方式,为了未来而生的编程学习平台,
          大部分资源是完全免费的,并且会根据当前互联网的变化实时更新本站内容。
        </div>
        <wrappedcomponent></wrappedcomponent>
      </div>
    }
  }
}
ログイン後にコピー

es7のデコレータを詳しく解説(例付き)

現時点では、元のコンポーネントの名前が DOM ツリーに正しく表示されます。

この単純な例では、上位コンポーネントはラップされたコンポーネントに

タイトル スタイル を追加するという 1 つのことだけを行います。この上位コンポーネントは、このロジックを追加する必要がある任意のコンポーネントで使用でき、この上位コンポーネントによってのみ変更する必要があります。

上位コンポーネントの主な機能は、コンポーネントの共通ロジックをカプセル化して抽象化し、ロジックのこの部分をコンポーネント間でより適切に再利用できるようにすることであることがわかります。

4. 上位コンポーネントの高度な使用法

4.1 コンポーネントのパラメーター

上記の例を例に挙げますが、この上位コンポーネントは情報のみを表示します。 Web サイトの説明についてですが、より適切に抽象化するには、この説明をパラメータ化して次のように呼び出す必要があります。

@withDescription('欢迎大家访问收藏(https://www.geekjc.com)') 
export default class Geekjc extends Component {
  render() {
    return (
      //...
    );
  }
}
ログイン後にコピー
withDescription は、パラメータを受け取り、上位のコンポーネント (関数) を返す次の形式に書き換える必要があります。

export default function (description) {
  return function (WrappedComponent) {
    return class HOC extends Component {
      render() {
        return <div>
          <div>
            {description
              ?description
              : '我是描述'}
          </div>
          <wrappedcomponent></wrappedcomponent>
        </div>
      }
    }
  }
}
ログイン後にコピー
ES6 を使用すると、より簡潔に記述できます。

export default(description) => (WrappedComponent) => class HOC extends Component {
  render() {
    return <div>
      <div>
        {description
          ? description
          : '我是描述'}
      </div>
      <wrappedcomponent></wrappedcomponent>
    </div>
  }
}
ログイン後にコピー
図からわかるように、渡されたパラメータが DOM ツリーに反映されています。

es7のデコレータを詳しく解説(例付き)

カリー化カリー

コンセプト: 関数のパラメータの一部だけを渡して呼び出し、残りのパラメータを処理する関数を返します。
関数シグネチャ: fun(params)(otherParams)
アプリケーション: React では、カリー化を通じて、さまざまなパラメーターを渡すことでさまざまな高次コンポーネントを取得できます。

4.2 属性プロキシに基づく方法

属性プロキシは、上位コンポーネントを使用する最も一般的な方法です。上で説明した上位コンポーネントはこの方法です。これは、ラップされたコンポーネントのプロパティと新しく生成されたプロパティをこのコンポーネントに渡すためにいくつかの操作を実行します。これはプロパティ プロキシと呼ばれます。

export default function withHeader(WrappedComponent) {
  return class HOC extends Component {
    render() {
      const newProps = {
        test:'hoc'
      }
      // 透传props,并且传递新的newProps
      return <p>
        <wrappedcomponent></wrappedcomponent>
      </p>
    }
  }
}
ログイン後にコピー
4.3 逆継承に基づく

この方法で返された React コンポーネントは、渡されたコンポーネントを継承するため、属性よりも多くの領域と権限にアクセスできます。組織化と変更。詳細については、付録にあるリンクを参照して詳しく学習してください。

export default function (WrappedComponent) {
  return class Inheritance extends WrappedComponent {
    componentDidMount() {
      // 可以方便地得到state,做一些更深入的修改。
      console.log(this.state);
    }
    render() {
      return super.render();
    }
  }
}
ログイン後にコピー

4.4 组合多个高阶组件

上述高阶组件为React组件增强了一个功能,如果需要同时增加多个功能需要怎么做?这种场景非常常见,例如我既需要增加一个组件标题,又需要在此组件未加载完成时显示Loading。

@withDescription
@withLoading
class Demo extends Component{

}
ログイン後にコピー

使用compose可以简化上述过程,也能体现函数式编程的思想。

const enhance = compose(withHeader,withLoading);
@enhance
class Demo extends Component{

}
ログイン後にコピー
组合 Compose
compose可以帮助我们组合任意个(包括0个)高阶函数,例如compose(a,b,c)返回一个新的函数d,函数d依然接受一个函数作为入参,只不过在内部会依次调用c,b,a,从表现层对使用者保持透明。
基于这个特性,我们便可以非常便捷地为某个组件增强或减弱其特征,只需要去变更compose函数里的参数个数便可。

compose函数实现方式有很多种,这里推荐其中一个recompact.compose,详情见下方参考类库,也可以看我之前写的一篇文章reduce与redux中compose函数

5. 与父组件区别

高阶组件作为一个函数,它可以更加纯粹地关注业务逻辑层面的代码,比如数据处理,数据校验,发送请求等,可以改善目前代码里业务逻辑和UI逻辑混杂在一起的现状。父组件则是UI层的东西,我们先前经常把一些业务逻辑处理放在父组件里,这样会造成父组件混乱的情况。为了代码进一步解耦,可以考虑使用高阶组件这种模式。

6. 开源的高阶组件使用

6.1 recompact

recompact提供了一系列使用的高阶组件,可以增强组件的行为,可以利用此库学习高阶组件的写法。

import recompact from 'recompact'
import { pure, withProps } from 'recompact'

const enhance = recompact.compose(
  withProps({ className: 'beautiful' }),
  pure,
)
@enhance
class Demo extends Component{

}
ログイン後にコピー

6.2 React Sortable

通过使用此库提供的高阶组件,可以方便地让列表元素可拖动。

以上がes7のデコレータを詳しく解説(例付き)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:segmentfault.com
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート