Non TypeScriptスタックで型のバリデーションをする機構を01で実装してみる


こんにちは(๑╹ω╹๑ )

↓ つい先日投稿した記事を多くの方に読んで頂けてとても嬉しいです(語彙力.....

「Vercel + freenom + Getform.io」構成で「爆速 + 管理費完全無料」のポートフォリオをGatsbyJSで作ったら幸福度が高まった話

今日はJavascript(主にTypeScriptスタックの無い環境)で型のバリデーションをする機構の実装について紹介します👼

少し特殊な書き方をしますが、可変メソッドを多用する立ち回りで実装できます😄

可変メソッドとは

  • メソッド名からメソッドを評価する

ことです。

PHPでは、

  • 可変変数
  • 可変関数
  • 可変クラス

等できるかと思いますが、
それに近しい立ち回りです🙆‍♂️

以前、PHPの言語仕様の理解を深める特殊な立ち回りでも紹介した下記のPHPコードを見てください👨‍💻

$orange_price = "300円";

$var = "orange_price";

echo $$var; //300円

↑のような立ち回りをJavascriptのメソッドでもしよう💪ということです!

実は簡単🤔 JavaScriptの可変メソッド😊

まず↓を眺めましょう🙌

console.log("Hoge")

言語仕様まで追っている訳ではありませんが、
consoleというクラスのlogという@staticmethodHogeというString型の値を渡して、
console.log("Hoge")を評価させていますよね🐥

consoleはオブジェクトだということも

分かるので

ということは、、、、!

console['log']("Hoge");

も同様に評価されるということです😲

よって、logというString型の値を定義しておくと、、、、

const Var = 'log';

console[Var]("Hoge");

のように可変メソッドで立ち回る事ができます🧐

可変メソッドをコールするメソッドを書いてみる

PHPのcall_user_func_arrayをJavascriptで実装してみます。
※メソッドのみ

↓書いてみました!


class Hoge {

    displaymMssage( mssage ){
        console.log(mssage);
    }

    callUserMethodArray(
        methodName,
        var
    ){
        this[methodName](var);
    }

}

const hoge = new Hoge;
hoge.callUserMethodArray(
    'displaymMssage',
    'Hoge!!!'
);
// => Hoge!!!

もちろん許容する引数の数は、
必ずしも1つでは無いので、
引数が複数である場合も考慮して書いてみました🥺


class Hoge {

    displaymMssage( mssage1, mssage2 ){
        console.log(mssage1);
        console.log(mssage2);
    }

    callUserMethodArray( methodName, vars ){
        this[methodName].apply( this, vars );
    }

}

const hoge = new Hoge;
hoge.callUserMethodArray(
    'displaymMssage',
    [ 'Hoge!!!', 'Foo!!!' ]
);
// => Hoge!!!
// => Foo!!!

applyで立ち回ると出来ますね!

これに可変プロパティを用いることも簡単です

class Hoge {

    constructor(){
      this.mssage1 = "Hoge!!!";
      this.mssage2 = "Foo!!!";
    }

    displaymMssage( var1, var2 ){
        console.log( this[var1] );
        console.log( this[var2] );
    }

    callUserMethodArray( methodName, vars ){
        this[methodName].apply( this, vars );
    }

}

const hoge = new Hoge;
hoge.callUserMethodArray(
    'displaymMssage',
    [ 'mssage1', 'mssage2' ]
);
// => Hoge!!!
// => Foo!!!

動的に型のバリデーションを行う

動的に可変メソッドを用いて立ち回ると、
Non TypeScriptスタックでも簡単にバリデーションチェックを行う機構を実装できます!


class Hoge {

    constructor(){
      this.validationRules = {
          displaymMssage: [
              "string",
              "boolean",
              "number",
          ]
      };
    }

    displaymMssage( mssage1, mssage2, mssage3 ){
        console.log(mssage1);
        console.log(mssage2);
        console.log(mssage3);
    }

    validationArguments( methodName, vars ){
      const rules = this.validationRules[methodName];
      return rules.map(
        ( rule, key ) => {
          return (
            typeof vars[key] === rule
            ? null
            : rule
          );
        }
      ).filter(Boolean);
    }

    callUserMethodArray( methodName, vars ){
        const validationResult = this.validationArguments( methodName, vars );
        if(validationResult.length > 0){
          const errorTypes = validationResult.join(" and ");
          return `Failed to validate the argument. Pass the correct ${errorTypes}.`;
        }
        this[methodName].apply( this, vars );
    }

}

const hoge = new Hoge;
const result = hoge.callUserMethodArray(
    'displaymMssage',
    [ 'Hoge!!!', 10, true ]
);

console.log(result);
// => Failed to validate the argument. Pass the correct boolean and number.

↑です!

簡単に解説をします🤗

this.validationRules = {
    displaymMssage: [
        "string",
        "boolean",
        "number",
    ]
};

Hogeクラスのconstructorで定義をした↑は、
displaymMssageというメソッドで許可する
引数順に文字列で定義しています。
※ サンプルコード内ではstring型、number型、boolean型の順番で実行させようとしています。

validationArgumentsメソッドでは、

const rules = this.validationRules[methodName];

↑で該当のルールをrulesで定義しています。

return rules.map(
    ( rule, key ) => {
        return (
            typeof vars[key] === rule
            ? null
            : rule
        );
    }
).filter(Boolean);

↑では、事前に定義した型とdisplaymMssageメソッドを実行するために渡された引数の型が
同一であるか確認しています。
その後、想定していない型であれば、
型名をstring型(rule)で返却します。
mapによって返却された配列を、

.filter(Boolean);

nullを除去しています。
ここでnullを除去出来るのは、

Boolean(null)

falseであるためです。

そして、callUserMethodArrayメソッド内では、
バリデーションエラーがあった場合に、
エラー分をreturnしています。

.filter(Boolean);

nullを除去していたので、
バリデーションエラーがあるのはvalidationArgumentsから返却された配列が空で無い時です。

これらの処理により、型のバリデーションに失敗した場合は想定のメソッドを評価させること無く、
例外エラーを差し込むことが出来ます🤗

[ 'Hoge!!!', true, 10 ]

を引数とすると、

// => Hoge!!!
// => true
// => 10

となって期待する処理の結果を得ることが出来ます。

まとめ

型のバリデーションを行うために、
ワンクッションメソッドを挟むことになり少し手間です。

どうしても、
「Non TypeScriptでないといけない😭
でも型のバリデーションはきちんと行いたい😭😭」
という場合には活用できそうですね😄