[キャッチアップ] タグ付きテンプレートリテラル
テンプレートリテラルはわかるけどタグ付きテンプレートリテラル(GraphQL 文脈で使われる gql
みたいなやつ) がなんなのかわかってなかったので確認する。
タグつきテンプレートリテラルは、リテラルから任意のテキストセグメントの配列と、任意の置換の値を引数として関数(タグ関数)を呼び出します。これは、 DSL に便利です。
日本語でOK
以下はタグなしテンプレートリテラルなので、文字列中のプレースホルダの式が展開されて文字列が返ってくるだけ。
const person = "Mike";
const age = 28;
const output = `That ${person} is a ${age}.`; // That Mike is a 28.
console.log(output);
これを myTag
というタグを使用するように書き換えると、タグ関数には引数としてテンプレートリテラル内のテキストとプレースホルダに挿入された値が入ってくる。
function myTag(...args) {
console.log(args); // [ [ 'That ', ' is a ', '.' ], 'Mike', 28 ]
}
const output = myTag`That ${person} is a ${age}.`;
引数に名前をつけて受け取るならこう
function myTag(strings, personExp, ageExp) {
// ...
}
タグ関数は任意の値を返す (返さず undefined にもできる) ことで、呼び出し元の評価値になる。
function myTag(strings, personExp, ageExp) {
return "Hello, Tagged Template Literal";
}
const output = myTag`That ${person} is a ${age}.`;
console.log(output); // Hello, Tagged Template Literal
この仕組を使ってパラメータに応じて改変した結果を返すことができる。
const person = "Mike";
const age = 28;
function myTag(strings, personExp, ageExp) {
const str0 = strings[0]; // "That "
const str1 = strings[1]; // " is a "
const str2 = strings[2]; // "."
const argStr = ageExp > 99 ? "centenarian" : "youngster";
return `${str0}${personExp}${str1}${argStr}${str2}`;
}
const output = myTag`That ${person} is a ${age}.`;
console.log(output); // That Mike is a youngster.
これだけだと何のために使えるのかマジでわからない
タグ付きテンプレートリテラルを有効活用してる事例として、 styled-components
がある。
こんな初見だとビビる書き方ができる。
import styled from "styled-components";
const Button = styled.a`
display: inline-block;
border-radius: 3px;
padding: 0.5rem 0;
${(props: any) =>
props.primary &&
styled.style`
background: white;
color: black;
`}
`;
console.log(Button);
テンプレートリテラルで文字列と関数を埋め込んでいるだけだが、実際はスタイルが適用されたコンポーネントが返ってくる。
{
'$$typeof': Symbol(react.forward_ref),
render: [Function: I] { displayName: 'styled.a' },
attrs: [],
componentStyle: e {
rules: [
'\n' +
' display: inline-block;\n' +
' border-radius: 3px;\n' +
' padding: 0.5rem 0;\n' +
'\n' +
' ',
[Function (anonymous)],
'\n',
isCss: true
],
staticRulesId: '',
isStatic: false,
componentId: 'sc-bcXHqe',
baseHash: 138803661,
baseStyle: undefined
},
shouldForwardProp: undefined,
foldedComponentIds: [],
styledComponentId: 'sc-bcXHqe',
target: 'a',
withComponent: [Function (anonymous)],
warnTooManyClasses: [Function (anonymous)],
toString: [Function (anonymous)]
}
もう一例、GraphQL クライアントの @apollo/client
では GraphQL のクエリをタグ付きテンプレートリテラルで書くことができる。
import { gql } from "@apollo/client";
const query = gql`
query {
totalUsers
user {
id
name
}
}
`;
console.log(query);
これはプレインテキストのクエリを元に、ASTが生成されるようになる。
{
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'query',
name: undefined,
variableDefinitions: [],
directives: [],
selectionSet: [Object]
}
],
loc: Location {
start: 0,
end: 67,
source: Source {
body: '\n query {\n totalUsers\n user {\n id\n name\n }\n }\n',
name: 'GraphQL request',
locationOffset: [Object]
}
}
}
結論、タグ関数を自前で実装する機会はなさそうだけど、文字列をベースにより複雑なオブジェクトを生成してくれる仕組みをタグ付きテンプレートリテラルで提供しているライブラリは結構あるみたいなので、存在は認識しておけば十分そう。