[JavaScript] template literals でなんちゃって string.format()


やりたかったこと

Enum を定義しているような場合に、共通メッセージ用のテンプレート文字列を定義しておいて、必要な時に placeholder に対して任意の値を入れて使いたい。

例えば Python だと、string.format() を使うことで下記のようにできます。

message = "[Error] code: {code}, message: {message}"

print(message.format(code="000", message="error message text"))

# >>> [Error] code: 000, message: error message text

JavaScript ではこれに該当するメソッドがないので、独自の実装を作って使っている人も多いみたいです (ショシンシャー なのでよくわからん)。皆大好き stackoverflow では Yahoo (.com) のライブラリの例として下記のようなコードが貼られてました。

YAHOO.Tools.printf = function() { 
  var num = arguments.length; 
  var oStr = arguments[0];   
  for (var i = 1; i < num; i++) { 
    var pattern = "\\{" + (i-1) + "\\}"; 
    var re = new RegExp(pattern, "g"); 
    oStr = oStr.replace(re, arguments[i]); 
  } 
  return oStr; 
} 

// YAHOO.Tools.printf('{0} {1} {2}', 'aaa', 'bbb', 'ccc'); 

template literals 使う

もうちょっと手軽でスマートな感じにしたい、そこで思いつくのは
ES6 で入ってきたおなじみの template literals ですが、

const templateString = `[Error] code: ${code}, message: ${message}`;

上記の場合の問題点は、事前に code / message 変数が定義されている必要があることです。
そこで、文字列を下記のように関数で囲ってしまいます。


const templateString = ({code, message}) => `[Error] code: ${code}, message: ${message}`;

また上記の例では分割代入を使っているので、呼び出し側では引数の順番を考慮する必要がなく、キーに対して値を指定するのでコードの可読性も高いと思います。


const templateString = ({code, message}) => `[Error] code: ${code}, message: ${message}`;

// どちらも出力は同じ
console.log(templateString({code: '000', message: 'error message text'}));
console.log(templateString({message: 'error message text', code: '000'}));

// >>> [Error] code: 000, message: error message text

これなら楽ですね!

番外編

実は上記の方法を知るまでは、Tagged template literals を使って同様のことをやろうとしていました。仕様はドキュメントを見てもらえればと思いますが、下記のようになります。


// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_templates
// コードは上記からまんまなので、実際にはテンプレート文字列中で、
// `${0}` のように、整数 or `${'keyword'}` 形式で指定可能
function template(strings, ...keys) {
    return (function(...values) {
        var dict = values[values.length - 1] || {};
        var result = [strings[0]];
        keys.forEach(function(key, i) {
            var value = Number.isInteger(key) ? values[key] : dict[key];
            result.push(value, strings[i + 1]);
        });
        return result.join('');
    });
}

const templateString = template`[Error] code: ${'code'}, message: ${'message'}`;

console.log(templateString({code: '000', message: 'error message text'}));
// >>> [Error] code: 000, message: error message text

関数を定義しておき、テンプレート文字列を定義する時にこの関数を"タグ付け"しておくことで、呼び出し時に任意の処理を加えることができます。今回の例では単純に文字列の結合処理のみでしたが、Tagged template literals は他にも色々と応用が効きそうです。

参考: Tagged Template literals — Its more than you think