Uzyskiwanie literału z ciągami znaków szablonu ES6

Addy Osmani
Addy Osmani

Ciągi tekstowe w języku JavaScript były w przeszłości ograniczone ze względu na brak możliwości, jakich można się spodziewać w takich językach jak Python czy Ruby. Ciągi tekstowe szablonów ES6 (dostępne w Chrome w wersji 41 i nowszych) całkowicie to zmieniają. Wprowadzają one sposób definiowania ciągów tekstowych w językach związanych z domeną (DSL), co zapewnia:

  • Interpolacja ciągu znaków
  • Osadzone wyrażenia
  • Wielowierszowe ciągi bez znaków hackingu
  • Formatowanie ciągów znaków
  • Tagowanie ciągów znaków zapewniające bezpieczne zmiany znaczenia kodu HTML, lokalizację i inne elementy.

Zamiast upychać kolejne funkcje w postaci ciągów tekstowych, jakie znamy obecnie, umożliwiają one rozwiązanie tych problemów w zupełnie inny sposób.

Składnia

Ciągi znaków szablonu korzystają ze znaków wstecznych (``), a nie pojedynczych lub podwójnych cudzysłowów, które są zwykle używane w przypadku zwykłych ciągów znaków. Ciąg szablonu mógłby więc wyglądać tak:

var greeting = `Yo World!`;

Do tej pory ciągi szablonów nie dały nam czegoś więcej niż zwykłe ciągi. Zmieńmy to.

Zastępowanie ciągu znaków

Jedną z pierwszych rzeczywistych korzyści jest zastąpienie ciągów znaków. Podstawianie umożliwia użycie dowolnego prawidłowego wyrażenia JavaScript (np. dodanie zmiennych), a wewnątrz literału szablonu wynik jest częścią tego samego ciągu znaków.

Ciągi znaków w szablonach mogą zawierać symbole zastępcze służące do zastępowania ciągów znaków przy użyciu składni ${ }, jak pokazano poniżej:

// Simple string substitution
var name = "Brendan";
console.log(`Yo, ${name}!`);

// => "Yo, Brendan!"

Wszystkie zastąpienia ciągów znaków w ciągach szablonów są wyrażeniami JavaScript, więc możemy wstawić o wiele więcej niż nazwy zmiennych. Na przykład poniżej możemy użyć interpolacji wyrażeń, aby umieścić w kodzie czytelną funkcję matematyczną w treści:

var a = 10;
var b = 10;
console.log(`JavaScript first appeared ${a+b} years ago. Wow!`);

//=> JavaScript first appeared 20 years ago. Wow!

console.log(`The number of JS MVC frameworks is ${2 * (a + b)} and not ${10 * (a + b)}.`);
//=> The number of JS frameworks is 40 and not 200.

Są też bardzo przydatne w przypadku funkcji wewnątrz wyrażeń:

function fn() { return "I am a result. Rarr"; }
console.log(`foo ${fn()} bar`);
//=> foo I am a result. Rarr bar.

${} działa dobrze z dowolnymi wyrażeniami, m.in. z wyrażeniami członkowskimi i wywołaniami metod:

var user = {name: 'Caitlin Potter'};
console.log(`Thanks for getting this into V8, ${user.name.toUpperCase()}.`);

// => "Thanks for getting this into V8, CAITLIN POTTER";

// And another example
var thing = 'template strings';
console.log(`Say hello to ${thing}.`);

// => Say hello to template strings

Jeśli chcesz, aby w ciągu znaków był lewy apostrof, możesz zmienić jego znaczenie przy użyciu znaku ukośnika lewego \ w ten sposób:

var greeting = `\`Yo\` World!`;

Ciągi wielowierszowe

Ciągi z wieloma wierszami w JavaScripcie od jakiegoś czasu wymagają obejścia. Obecne rozwiązania wymagają, aby ciągi tekstowe znajdowały się w jednym wierszu lub były podzielone na ciągi wielowierszowe za pomocą ukośnika lewego przed każdym nowym wierszem: \. Na przykład:

var greeting = "Yo \
World";

W większości nowoczesnych silników JavaScriptu powinno to działać dobrze, ale samo jego działanie jest trochę uciążliwe. Można też użyć konkatenacji ciągów znaków, aby fałszywie obsługiwać wiele wierszy, ale jednocześnie pozostawia to pożądaną obsługę:

var greeting = "Yo " +
"World";

Ciągi tekstowe szablonów znacznie upraszczają ciągi tekstowe w wielu wierszach. Po prostu wstaw nowe wiersze tam, gdzie są potrzebne, i WYRAŻ POCZĄTEK. Oto przykład:

Wszelkie odstępy w składni lewy również będą uważane za część ciągu znaków.

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

Otagowane szablony

Do tej pory omówiliśmy używanie ciągów szablonów do zastępowania ciągów znaków i tworzenia ciągów wielowierszowych. Kolejną niezwykle przydatną funkcją są szablony tagowania. Otagowane szablony przekształcają ciąg znaków szablonu, umieszczając nazwę funkcji przed ciągiem znaków szablonu. Na przykład:

fn`Hello ${you}! You're looking ${adjective} today!`

Znaczenie oznaczonego ciągiem znaków szablonu bardzo się różni od jego zwykłego ciągu. W skrócie są to specjalne wywołanie funkcji: wymienione wyżej „desugary”

fn(["Hello ", "! You're looking ", " today!"], you, adjective);

Zwróć uwagę, że argument (n + 1) odpowiada podstawieniu dokonanemu między n-tą a (n + 1) wpisem w tablicy ciągów. Jest to przydatne w każdej sytuacji, ale jedna z najprostszych to automatyczne zmiany znaczenia wszystkich interpolowanych zmiennych.

Można na przykład utworzyć funkcję zmiany znaczenia kodu HTML w taki sposób,

html`<p title="${title}">Hello ${you}!</p>`

zwraca ciąg znaków z zastąpionymi odpowiednimi zmiennymi, ale zamienia wszystkie znaki, które nie są bezpieczne dla HTML. Zrobimy to razem. Nasza funkcja zmiany znaczenia kodu HTML przyjmuje dwa argumenty: nazwę użytkownika i komentarz. Oba mogą zawierać niebezpieczne znaki HTML (czyli ', , <, >, i &). Jeśli na przykład nazwa użytkownika to „Domenic Denicola”, a komentarz to „& to ciekawy tag”, powinno wyświetlić się polecenie:

<b>Domenic Denicola says:</b> "&amp; is a fun tag"

Nasze otagowane rozwiązanie szablonu wygląda więc tak:

// HTML Escape helper utility
var util = (function () {
    // Thanks to Andrea Giammarchi
    var
    reEscape = /[&<>'"]/g,
    reUnescape = /&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34);/g,
    oEscape = {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        "'": '&#39;',
        '"': '&quot;'
    },
    oUnescape = {
        '&amp;': '&',
        '&#38;': '&',
        '&lt;': '<',
        '&#60;': '<',
        '&gt;': '>',
        '&#62;': '>',
        '&apos;': "'",
        '&#39;': "'",
        '&quot;': '"',
        '&#34;': '"'
    },
    fnEscape = function (m) {
        return oEscape[m];
    },
    fnUnescape = function (m) {
        return oUnescape[m];
    },
    replace = String.prototype.replace
    ;
    return (Object.freeze || Object)({
    escape: function escape(s) {
        return replace.call(s, reEscape, fnEscape);
    },
    unescape: function unescape(s) {
        return replace.call(s, reUnescape, fnUnescape);
    }
    });
}());

// Tagged template function
function html(pieces) {
    var result = pieces[0];
    var substitutions = [].slice.call(arguments, 1);
    for (var i = 0; i < substitutions.length; ++i) {
        result += util.escape(substitutions[i]) + pieces[i + 1];
    }

    return result;
}

var username = "Domenic Denicola";
var tag = "& is a fun tag";
console.log(html`<b>${username} says</b>: "${tag}"`);
//=> <b>Domenic Denicola says</b>: "&amp; is a fun tag"

Inne możliwe zastosowania obejmują automatyczne zmiany znaczenia, formatowanie, lokalizację i, ogólnie rzecz biorąc, bardziej złożone zamienniki:

// Contextual auto-escaping
qsa`.${className}`;
safehtml`<a href="${url}?q=${query}" onclick="alert('${message}')" style="color: ${color}">${message}</a>`;

// Localization and formatting
l10n`Hello ${name}; you are visitor number ${visitor}:n! You have ${money}:c in your account!`

// Embedded HTML/XML
jsx`<a href="${url}">${text}</a>` // becomes React.DOM.a({ href: url }, text)

// DSLs for code execution
var childProcess = sh`ps ax | grep ${pid}`;

Podsumowanie

Ciągi tekstowe szablonów są dostępne w przeglądarkach Chrome 41 beta i nowszych, IE Tech Preview, Firefox w wersji 35 i nowszych oraz io.js. Ogólnie rzecz biorąc, jeśli chcesz używać ich w produkcji, są one obsługiwane przez główne transpilatory ES6, w tym Traceur i 6to5. Jeśli chcesz je wypróbować, zapoznaj się z przykładem z ciągiem szablonów w repozytorium z przykładami Chrome. Być może zainteresuje Cię też artykuł ES6 Equivalents in ES5, w którym pokazujemy, jak dzięki ES5 uzyskać niektóre z ciągów tekstowych szablonów.

Ciągi szablonów zapewniają wiele ważnych funkcji JavaScriptu. Obejmują one lepsze metody interpolacji ciągów i wyrażeń, ciągi wielowierszowe oraz możliwość tworzenia własnych DSL.

Jedną z najważniejszych funkcji tego rozwiązania są szablony tagowania, które są kluczowym elementem tworzenia takich DSL. Otrzymają one części ciągu szablonu jako argumenty, a następnie możesz zdecydować, jak użyć ciągów i podstawień, aby określić ostateczny rezultat.

Dalsza lektura