テンプレートリテラル (テンプレート文字列)

テンプレートリテラルはバックティック (`) で区切られたリテラルで、substitutionと呼ばれる埋め込み式が利用できます。

タグなしテンプレートリテラルは文字列となるため、文字列の補間に便利です(エスケープされていない改行が許されるため、複数行の文字列も可能です)。 タグつきテンプレートリテラルは、リテラルから任意のテキストセグメントの配列と、任意の置換の値を引数として関数(タグ関数)を呼び出します。これは、 DSL に便利です。

テンプレートリテラルは非公式にテンプレート文字列と呼ばれることもありますが、文字列リテラルではないので、文字列リテラルが使えるところならばどこでも使えるわけではありません。また、タグ付けされたテンプレートリテラルが文字列になるとは限りません。何を作成するかはタグ関数次第です(もし何か作成するのであれば)。

構文

// タグなし。これらは文字列を生成します。
`string text`

`string text line 1
 string text line 2`

`string text ${expression} string text`

// タグを付けると、関数 "example" を最初の引数にテンプレート、
// 後続の引数に置換値を指定して呼び出します。
example`string text ${expression} string text`

解説

テンプレートリテラルは、ダブルクォートやシングルクォートの代わりにバックティック文字 (` `) (グレーブアクセント)で囲みます。

テンプレートリテラルにはプレースホルダーを含めることができます。プレースホルダーはドル記号と波括弧 (${expression}) で示されます。プレースホルダー内の式とバックティック文字 (` `) の間にあるテキストが関数に渡されます。

既定の関数は、単に各部を連結して 1 つの文字列とするだけです。テンプレートリテラルの前に式(ここでは tag)がある場合、これはタグ付きテンプレートと呼ばれます。この場合、タグ式(通常は関数)はテンプレートリテラルとともに呼び出され、出力する前にそれを操作することができます。

テンプレートリテラル内でバックティック文字をエスケープするには、バックティック文字の前にバックスラッシュ (\) を置きます。

`\`` === '`' // --> true

複数行の文字列

ソースの中に挿入された改行文字は、すべてテンプレートリテラルの一部になります。

通常の文字列を使う場合は、複数行の文字列を得るために次のような構文を使用する必要があります。

console.log('string text line 1\n' +
'string text line 2');
// "string text line 1
// string text line 2"

テンプレートリテラルを使用すれば、同じものを次のように書くことができます。

console.log(`string text line 1
string text line 2`);
// "string text line 1
// string text line 2"

式の挿入

通常の文字列に式を埋め込むには、以下のような構文を使用していると思います。

let a = 5;
let b = 10;
console.log('Fifteen is ' + (a + b) + ' and\nnot ' + (2 * a + b) + '.');
// "Fifteen is 15 and
// not 20."

テンプレートリテラルを使用すると、糖衣構文を使用して、このようにもっと読みやすく表記することができます。

let a = 5;
let b = 10;
console.log(`Fifteen is ${a + b} and
not ${2 * a + b}.`);
// "Fifteen is 15 and
// not 20."

入れ子のテンプレート

場合によっては、テンプレートを入れ子にすると、構成可能な文字列を得るのにもっとも簡単に (かつ、おそらくより読みやすく) なることがあります。バックティックをつけたテンプレートでは、テンプレート内にあるプレイスホルダー ${ } の内部で内部のバックティックを使用することができます。

例えば、条件式が true の場合、このテンプレートリテラルを return します。

ES5 の場合:

let classes = 'header';
classes += (isLargeScreen() ?
  '' : item.isCollapsed ?
    ' icon-expander' : ' icon-collapser');

ES2015 で入れ子なしのテンプレートリテラルの場合:

const classes = `header ${ isLargeScreen() ? '' :
  (item.isCollapsed ? 'icon-expander' : 'icon-collapser') }`;

ES2015 で入れ子にしたテンプレートリテラルの場合:

const classes = `header ${ isLargeScreen() ? '' :
  `icon-${item.isCollapsed ? 'expander' : 'collapser'}` }`;

タグ付きテンプレート

タグ付きテンプレートは、テンプレートリテラルのより高度な形式です。

タグを使用すると、テンプレートリテラルを関数で解析できます。タグ関数の最初の引数には、文字列リテラルの配列を含みます。残りの引数は式に関連付けられます。

タグ関数は、これらの引数に対して何でも望み通りの操作を実行することができ、加工された文字列を返します。(または、以下の例の一つで示しているように、まったく異なるものを返すこともできます。)

タグに使用される関数の名前は、自由に指定できます。

let person = 'Mike';
let age = 28;

function myTag(strings, personExp, ageExp) {
  let str0 = strings[0]; // "That "
  let str1 = strings[1]; // " is a "
  let str2 = strings[2]; // "."

  let ageStr;
  if (ageExp > 99){
    ageStr = 'centenarian';
  } else {
    ageStr = 'youngster';
  }

  // テンプレートリテラルを用いて組み立てた文字列を返すこともできます
  return `${str0}${personExp}${str1}${ageStr}${str2}`;
}

let output = myTag`That ${ person } is a ${ age }.`;

console.log(output);
// That Mike is a youngster.

タグ関数は文字列を返さなくても構いません。

function template(strings, ...keys) {
  return (function(...values) {
    let dict = values[values.length - 1] || {};
    let result = [strings[0]];
    keys.forEach(function(key, i) {
      let value = Number.isInteger(key) ? values[key] : dict[key];
      result.push(value, strings[i + 1]);
    });
    return result.join('');
  });
}

let t1Closure = template`${0}${1}${0}!`;
//let t1Closure = template(["","","","!"],0,1,0);
t1Closure('Y', 'A');                      // "YAY!"

let t2Closure = template`${0} ${'foo'}!`;
//let t2Closure = template([""," ","!"],0,"foo");
t2Closure('Hello', {foo: 'World'}); // "Hello World!"

let t3Closure = template`I'm ${'name'}. I'm almost ${'age'} years old.`;
//let t3Closure = template(["I'm ", ". I'm almost ", " years old."], "name", "age");
t3Closure('foo', {name: 'MDN', age: 30}); //"I'm MDN. I'm almost 30 years old."
t3Closure({name: 'MDN', age: 30}); //"I'm MDN. I'm almost 30 years old."

加工前の文字列

タグ関数に渡される第 1 引数では、特別な raw プロパティが利用できます。このプロパティを通して、エスケープシーケンスを処理する前の、入力された通りの加工前の文字列を参照することができます。

function tag(strings) {
  console.log(strings.raw[0]);
}

tag`string text line 1 \n string text line 2`;
// logs "string text line 1 \n string text line 2" ,
// including the two characters '\' and 'n'

加えて、 String.raw() メソッドがあり、既定のテンプレート関数のように文字列を連結した形で加工前の文字列を生成することができます。

let str = String.raw`Hi\n${2+3}!`;
// "Hi\\n5!"

str.length;
// 6

Array.from(str).join(',');
// "H,i,\\,n,5,!"

タグ付きテンプレートとエスケープシーケンス

ES2016 での動作

ECMAScript 2016 時点では、タグ付きテンプレートの以下のエスケープシーケンスには、次のようなルールが適用されます。

  • Unicode のエスケープシーケンスは "\u" で始まること。例: \u00A9
  • Unicode のコードポイントのエスケープは "\u{}" で示すこと。例: \u{2F804}
  • 16 進数のエスケープは "\x" で始まること。例: \xA9
  • 8 進数のリテラルのエスケープは "\0o" で始まり、その後数字が続くこと。例: \0o251

このルールのもとでは、下に示す例のようなタグ付きテンプレートが問題となります。なぜなら、 ECMAScript の文法に従ってこのテキストを解釈しようとすると、パーサーは Unicode の有効のエスケープシーケンスを探そうとするも、不正な構文が検出されてしまうからです。

latex`\unicode`
// 古い ECMAScript バージョン (ES2016 以前) では、以下のような例外が投げられる
// SyntaxError: malformed Unicode character escape sequence

ES2018 revision での不正なエスケープシーケンス

タグ付きテンプレートでは、他のエスケープシーケンスが一般的な言語 (DSLLaTeX など) の埋め込みも許容する必要があります。 ECMAScript のプロポーザル Template Literal Revision (Stage 4, to be integrated in the ECMAScript 2018 standard) では、タグ付きテンプレートから、ECMAScript のエスケープシーケンスについての構文的な制約が除外されています。

しかし、埋め込んで「加工された」結果の中においても、不正なエスケープシーケンスがあれば、それを提示する必要があります。こうした不正なエスケープシーケンスは、「加工された」の配列の中では、undefined の要素として表示されます。

function latex(str) {
  return { "cooked": str[0], "raw": str.raw[0] }
}

latex`\unicode`

// { cooked: undefined, raw: "\\unicode" }

このエスケープシーケンスの制約は、タグ付きテンプレートのみが除外となります。タグ付きでないテンプレートリテラルの場合は、除外対象ではありません:

let bad = `bad escape sequence: \unicode`;

仕様書

Specification
ECMAScript Language Specification
# sec-template-literals

ブラウザーの互換性

BCD tables only load in the browser

関連情報