LoginSignup
27
24

More than 5 years have passed since last update.

また TypeScript で引数のデフォルト値処理とか

Last updated at Posted at 2017-04-29

オプション引数なんて可能な限り減らすべきだよなと思いつつ、うまい実装方法はないものかと考えるのをやめられません。楽しいですし。

TypeScript では引数にデフォルト値を指定でき、かつ引数オブジェクトの destructuring も行えそこにもデフォルト値を指定できるので、きっとこれを利用したオプション値の設定手法も何かしらあるのだろうと調べてみたところ 2015/11 (結構前だ...)の記事を見つけたので、真似してやってみました。

Typing Destructured Object Parameters in TypeScript

二つのフィールド o1o2 を持つオプションオブジェクトを func の第二引数に渡しています。

function func(v: any, {
  o1 = 'default-value',
  o2 = 1000,
}: { o1?: string, o2?: number } = {}) {
  console.log(o1, o2);
}

個々のプロパティが省略されている場合はそれぞれのデフォルト値を適用し、オプションオブジェクト自体が省略されている場合は空のオブジェクト {} が適用され、次いで個々のプロパティのデフォルト値が適用される、みたいな処理順なのでしょうか。

これで期待通りに動作するのでそういうことなのでしょうね。

JavaScript では

吐き出された JavaScript のコードを見てみると

function func(str, _a) {
    var _b = _a === void 0 ? {} : _a, _c = _b.o1, o1 = _c === void 0 ? 'default-value' : _c, _d = _b.o2, o2 = _d === void 0 ? 1000 : _d;
    console.log(o1, o2);
}

のようになっています。デフォルト値の部分を頑張って折り返すと

    // オプションオブジェクト自体のデフォルト値処理
var _b = (_a === void 0) ? {} : _a,

    // o1 のデフォルト値処理
    _c = _b.o1,
    o1 = _c === void 0 ? 'default-value' : _c,

    // o2 のデフォルト値処理
    _d = _b.o2,
    o2 = _d === void 0 ? 1000 : _d;

ですね。ほどいてみると意外に整然としていてびっくりしました。式中に出てくる void 0本当undefined を返す JavaScript のイディオムみたいです(参考)。

Partial を使ってみる

ちなみにですが、最近使えるようになった TypeScritp の Partial を利用すると

function func(v: any, {
  o1 = 'default-value',
  o2 = 1000,
}: Partial<{ o1: string, o2: number }> = {}) {
  console.log(o1, o2);
}

こう書けたりもしますね。メンバ全てがオプショナルな場合は ? をつけ忘れるのを気にしなくても済むので私はこちらの方が好きです。

インタフェース定義を分ける

さすがにごちゃごちゃしているのでインタフェース定義だけ別に書くとこんな感じになります。

interface Options {
  o1: string,
  o2: number,
}

function func(v: any, {
  o1 = 'default-value',
  o2 = 1000,
}: Partial<Options> = {}) {
  console.log(o1, o2);
}

まずまずの読みやすさでしょうか。

ネストした状態のものも色々試したり探したりしていたのですが見つからなかったので今回はここまでです。

ネストする

...で、後日ですが見つけました。ネストすると邪魔なので Partial は外してしまいました。

type Options = {
  o1?: string,
  o2?: number,
  o3?: {           // ネストしたオプション定義
    o3_a?: string; //
    o3_b?: number; //
  }                //
}

function func(v: any, {
  o1 = 'default-value',
  o2 = 1000,
  o3: {                    // ネストしたオプション
    o3_a = 'nested-value', // 
    o3_b = 2000,           //
  } = {}                   // o3 用の空オブジェクト
}: Options = {}) {         // オプション全体用の空オブジェクト
  console.log(o1, o2);
}

=: の使い分けがかなりややこしかったです... オプションオブジェクトには「内部のデフォルト値定義を行ってから空オブジェクト」が基本構造のようですね。こちらも出力された JavaScript を覗いてみると

function func(v, _a) {

    // オプションオブジェクト全体が定義されていない際ののデフォルト値処理
    var _b = _a === void 0 ? {} : _a,

      _c = _b.o1, o1 = _c === void 0 ? 'default-value' : _c,
      _d = _b.o2, o2 = _d === void 0 ? 1000 : _d,

      // ネストしたオプションオブジェクト `o3` 自体が定義されていない際のデフォルト値処理
      _e = _b.o3, _f = _e === void 0 ? {} : _e,

        // `o3` のプロパティのデフォルト値処理
        _g = _f.o3_a,o3_a = _g === void 0 ? 'nested-value' : _g,
        _h = _f.o3_b, o3_b = _h === void 0 ? 2000 : _h;

    console.log(o1, o2);
}

長くなるので折り返し方をちょっと変えました。特に問題なくネストした部分も処理されていますね。

そしてこれも後日気づいたのですが --target ES2015 を指定するとそもそもデフォルト値処理の部分は特に変更されずに吐き出されるんですね... でも処理の順序が理解できたので良かったです。

27
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
24