ES6の新機能「class構文」 – 実践編 –

ES6の新機能「class構文」 – 実践編 –

じぇしー

じぇしー

こんにちは新人フロントエンドエンジニアのじぇしーです。

ECMAScript連載の第4回は、前回の実践編として、classを利用して業務でよくありそうなスライドパネルの実装をしていきたいと思います。(本記事は、基礎編の後にお読みになることをおすすめします)

とりあえず普通にスライドパネルを作ってみる

それでは早速classの秘めたる力を体感していきましょう! まずは、classのことを気にせず普通にスライドパネルを作ってみます。

.js-toggle のついたボタンをクリックしたら、 .js-target classのついたスライダーが開閉するというシンプルな実装です。

これに .js-close をクリックしたらスライダーを閉じ、 .js-open をクリックしたらスライダーを開く処理を加えてみます。スライダー .js-target に状態管理用のclass .is-active を持たせ、そのclassの有無によってスライダーが開いているか閉じているかを判断しています。 data-*属性を使っても同様のことを実現できます。

これでひとまず完成しました。この程度なら、classを使わなくても見通しよく実装していけますよね。

classの継承・状態管理

ところが後日、 「最小の高さを持つスライドパネルをもうひとつ追加して欲しい」と言われたとしましょう。最初に作ったスライドパネルでは、スライド部分の実装にjQueryの toggleSlide を使用し、ターゲットのDOMの高さを完全に0にしてしまいます。

prototypeやclass(prototypeによる継承の糖衣構文)による継承を利用せずに実装するのであれば、まったく違う関数として実装するしかありません。

継承……あるclassが別のclassから特性を引き継ぐことを指します。継承の詳細は、前回の記事を参照ください。
classの継承 – ES6の新機能「class構文」 基礎編

しかし、classを使って書くと、親class Slider を継承する SliderAlt を作ることで、親classの処理を利用しつつ、少しだけ違う処理を実行することができます。classは、このように 「ほとんど同じだけど少しだけ違う処理」を書きたいときに役に立ちます。

改めて、コードの内容を見ていきましょう。

コード先頭で class class名 {} とすることでclassを定義しています。最初に定義されているSlider.slider-a に適用されている基本class(親class)で、その後定義されている SliderExSlider を引き継いだclassです。 SliderEx.slider-b に適用されています。

classを引き継ぐには、 class 子class名 extends 基本class名 {} とします。

class Slider {
  // ...
}

class SliderEx extends Slider {
  // ...
}

基本classを引き継いだ子classは、親classに定義された全てのmethodにアクセスすることができます。

method……class内で定義された一連の処理のこと。class専用の関数のようなものです。

親classのmethodにアクセスするには、以下のようにmethod内で super() を呼び出します。

class SliderEx extends Slider {
      constructor ($target) {
        super($target);
        this.minHeight;
        this.maxHeight;
      }

      // ...
    }

すると、親classの呼び出し元と同名のmethodが呼び出されます。(親classの constructor methodが呼び出される)

ここで constructor というmethodが登場しましたが、これはclassを new キーワードで初期化したとき、自動的に実行される特別なmethodです。 constructor の使い方に決まりはありませんが、私は基本的にclass内methodで使い回すオブジェクトや値を this オブジェクトに代入するのみにとどめ、それ以上の処理は initi methodで実行するようにしています。(init というmethod名は任意の名前なので、例えば initialize などという名前でも問題ありません)

this とは、classを new キーワードで初期化したときに生成されるオブジェクト(=インスタンス)自身を指し、各methodから this.オブジェクト名 という形でアクセスできます。インスタンスの状態を管理したり、各methodが共通で参照するDOMオブジェクトなどを代入したりします。

classを定義するコツとして、class内の各methodはできるだけ単一の役割だけを持たせシンプルにしておくと、あとで変更しやすいclassになります。

// ...
      init () {
        this.close();
      }

      open () {
        this.slide(this.$target, true);
        this.isOpen = true;
      }

      close () {
        this.slide(this.$target, false);
        this.isOpen = false;
      }
    // ...

コード例を見ると openclose のような短いmethodが多く定義されていて、それらを別のmethodが呼び出すことでclassが機能していることがわかると思います。

classの定義をしたら、あとはclassに new キーワードをつけて実行する(=初期化する)だけです。初期化するとclassはインスタンスを返します。インスタンスとはclassを実際に機能するように整えたオブジェクトです。

var $wrapperA = $('.slide-a');
    var sliderA = {
      $toggle: $wrapperA.find('.js-toggle'),
      $open: $wrapperA.find('.js-open'),
      $close: $wrapperA.find('.js-close'),
      $target: $wrapperA.find('.js-target')
    }

    var $wrapperB = $('.slide-b');
    var sliderB = {
      $toggle: $wrapperB.find('.js-toggle'),
      $open: $wrapperB.find('.js-open'),
      $close: $wrapperB.find('.js-close'),
      $target: $wrapperB.find('.js-target')
    }

    var SliderInstance = new Slider(sliderA.$target);
    var SliderAltInstance = new SliderAlt(sliderB.$target);

classは、定義した段階では具体的なDOMオブジェクトなどを一切持たない、いわば未実行の関数のようなもの。そのため、classにDOMオブジェクトを持たせて、classが実際に機能するようにする必要があるのです。(実際にコンパイル後のコードを見ると、classは単なる関数として実装されます)

sliderA.$open.on('click', function () {
      SliderInstance.open();
    });
    sliderA.$close.on('click', function () {
      SliderInstance.close();
    });
    sliderA.$toggle.on('click', function () {
      SliderInstance.toggle();
    });

    sliderB.$open.on('click', function () {
      SliderAltInstance.open();
    });
    sliderB.$close.on('click', function () {
      SliderAltInstance.close();
    });
    sliderB.$toggle.on('click', function () {
      SliderAltInstance.toggle();
    });

その後、各インスタンスが持つDOMオブジェクトと、各ボタンに対するクリックイベントを紐付けます。(私の好みですが、ここの処理はclass外ではなくclass内で紐付けます)

また、お気付きの方もいるかと思いますが、classを使ったコードでは、状態の管理をターゲットDOMの .is-active classの有無によってではなく、class内部の変数(メンバ変数)によって判断しています。

状態の管理を data-*属性やclassなどのDOMに直接持たせてしまうと、誰でも外部から状態の変更( .is-active の付け外し)ができてしまい、意図せずに状態を変更してしまったり、状態管理用のclass名がバッティングしてしまったりするといった問題があるため、状態はclass内で管理した方がいいです。

継承の効果的な使い方

ここまで実例を見てきたように、classは継承を活用することで同じコードを書かなくて済むようになるという利点があります。しかし、最初から拡張することを意識して再利用性の高いclassを設計するためには、ある程度のプログラミング経験や慣れが必要となります。

そこで、最初のうちは書籍などで広く知られている定型的な処理をclass化するなどして、まずはclass継承の感覚に慣れていくと良いかもしれません。

例えば、こちらのコードでは、Observerパターン(Publish/subscribe)の基本的な機能を実装した小さな PubSub classを Foo に継承させています。

Observerパターン(Publish/subscribe)……簡単に言うとjQueryのカスタムイベントのようなものですが、DOMに紐付いていないという点が異なります。

なお、このPubSub classは『シングルページWebアプリケーション ―Node.js、MongoDBを活用したJavaScript SPA』を参考に、作成しました。

おわりに

class構文の利点は、今回ご紹介した継承や状態管理の他にも、機能の外部モジュール化の際に利用したり、呼び出しと処理が明確に分離されることでバグが発生した際に問題の切り分けが容易になったりと、さまざまです。

プログラミングを始めたばかりの頃は、なかなかclass構文の利点を実感しにくいかと思いますが、class構文を利用して処理をまとめるだけでもclass構文の利点を実感できると思いますので、ぜひ積極的に利用してみてください。

LIGはWebサイト制作を支援しています。ご興味のある方は事業ぺージをぜひご覧ください。

Webサイト制作の実績・料金を見る

この記事のシェア数

じぇしー
じぇしー フロントエンドエンジニア / 村井 泰人

布を触るのもパソコンをさわるのも大好きなエンジニアです。よろしくお願いします。

このメンバーの記事をもっと読む
ES6のある星に生まれて | 8 articles