@peccul is peccu

(love peccu '(emacs lisp cat outdoor bicycle mac linux coffee))

ES6で動的に親クラスを指定する

引数に応じて親クラスを切り替えたいことがあって調べて、Dynamic super classes (extends) in ES6 – MikeDoesWeb が出てきたので試した。

おそらく私はmix-inみたいなことをしたかったのだと思う。

サンプルコードとそれのテスト(使い方の例になると思う)はGitHubに置いた。

github.com

解決法の解説/概要

クラスを直接定義するのではなく、関数スコープ内で定義することで解決していた。

実際に私がやりたかったのはNode.jsで動くならfsを使うクラスを継承して、ブラウザで動くならBrowserFSを使うクラスを継承したかった。 外からみると同じAPIにしたかったので、環境か引数によって必要なバックエンドのクラスを切り替えてテストしたかった。

以下GitHubに置いた解決策の抜粋。

// fsを使うクラス
export class FS {
  constructor(config){
  }
}

// BrowserFSを使うクラス
export class BrowserFS {
  constructor(config){
  }
}

// fallbackクラス
export class DefaultClass {
  constructor(config){
  }
};

// 外からはこの擬似クラス(実態は関数)を使う
const Dynamic = function(config = {}){
  if(!new.target){
    throw "Uncaught TypeError: Class constructor Dynamic cannot be invoked without 'new'";
  }
  // Dynamicというクラスを引数に応じて定義する

  if(config.backend === 'fs'){
    // FSを継承するパターン
    class Dynamic extends FS {
      constructor(config){
      }
    };
    return new Dynamic(config);
  }

  if(config.backend === 'browserfs'){
    // BrowserFSを継承するパターン
    class Dynamic extends BrowserFS {
      constructor(config){
      }
    };
    return new Dynamic(config);
  }
  // fallback
  return new DefaultClass(config);
};
export default Dynamic;

es6-dynamic-class-extends/dynamic.js at master · peccu/es6-dynamic-class-extends · GitHub

使用法はテストコードでイメージできるかと。 (Jest利用、同じく抜粋)

Dynamicはクラスではなく関数なので、instanceof Dynamic はfalseとなる。

import {default as Dynamic, FS, BrowserFS, DefaultClass} from './dynamic';

describe('Dynamically switching parent class', () => {
  test('non specified class', () => {
    let target = new Dynamic();
    expect(target).toBeInstanceOf(DefaultClass);
  });
  test('extends FS', () => {
    let target = new Dynamic({backend: 'fs'});
    expect(target).toBeInstanceOf(FS);
  });
  test('extends BrowserFS', () => {
    let target = new Dynamic({backend: 'browserfs'});
    expect(target).toBeInstanceOf(BrowserFS);
  });
});

es6-dynamic-class-extends/dynamic.test.js at master · peccu/es6-dynamic-class-extends · GitHub

c.f.

github.com

environment

  • .babelrc
{
  "presets": ["@babel/preset-env"],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "regenerator": true
      }
    ]
  ]
}
  • package.json
...
  "scripts": {
    "test": "jest"
  },
  "devDependencies": {
    "@babel/core": "^7.2.2",
    "@babel/plugin-transform-runtime": "^7.2.0",
    "@babel/preset-env": "^7.2.3",
    "@babel/runtime": "^7.2.0",
    "babel-core": "^7.0.0-bridge.0",
    "babel-jest": "^23.6.0",
    "jest": "^23.6.0"
  },
...