Como conseguir o literal com strings de modelo ES6

Addy Osmani
Addy Osmani

As strings em JavaScript sempre foram limitadas, não tendo os recursos que se esperam de linguagens como Python ou Ruby. As strings de modelo ES6 (disponíveis no Chrome 41 e versões mais recentes) mudam isso de forma fundamental. Eles apresentam uma maneira de definir strings com linguagens específicas de domínio (DSLs), trazendo uma melhor:

  • Interpolação de string
  • Expressões incorporadas
  • Strings de várias linhas sem hacks
  • Formatação de string
  • Marcação de strings para codificação de saída segura de HTML, localização e muito mais.

Em vez de encher Strings como as conhecemos hoje, as Strings de modelo apresentam uma maneira completamente diferente de resolver esses problemas.

Sintaxe

As strings de modelo usam acento grave (``) em vez de aspas simples ou duplas, como nas strings comuns. Uma string de modelo pode ser escrita da seguinte maneira:

var greeting = `Yo World!`;

Até agora, as strings de modelo não nos deram nada além das strings normais. Vamos mudar isso.

Substituição de string

Um dos primeiros benefícios reais é a substituição de strings. A substituição permite que qualquer expressão JavaScript válida (incluindo a adição de variáveis) seja usada em um literal de modelo, e o resultado será exibido como parte da mesma string.

As strings de modelo podem conter marcadores de posição para substituição de string usando a sintaxe ${ }, conforme demonstrado abaixo:

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

// => "Yo, Brendan!"

Como todas as substituições de string em strings de modelo são expressões JavaScript, podemos substituir muito mais do que nomes de variáveis. Por exemplo, abaixo, podemos usar a interpolação de expressões para incorporar algumas operações matemáticas legíveis:

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.

Elas também são muito úteis para funções dentro de expressões:

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

O ${} funciona bem com qualquer tipo de expressão, incluindo expressões de membro e chamadas de método:

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

Se você precisar de crases dentro da string, elas podem ser ignoradas usando o caractere barra invertida \ da seguinte maneira:

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

Strings de várias linhas

As strings de várias linhas em JavaScript exigem soluções alternativas há algum tempo. As soluções atuais exigem que as strings existam em uma única linha ou sejam divididas em strings de várias linhas usando um \ (barra invertida) antes de cada nova linha. Exemplo:

var greeting = "Yo \
World";

Embora isso funcione bem na maioria dos mecanismos modernos de JavaScript, o comportamento ainda é um pouco hackeado. Também é possível usar a concatenação de strings para simular o suporte a várias linhas, mas isso não é ideal:

var greeting = "Yo " +
"World";

As strings de modelo simplificam bastante as strings de várias linhas. Basta incluir quebras de linha onde for necessário. Veja um exemplo:

Qualquer espaço em branco dentro da sintaxe de acento grave também será considerado parte da string.

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

Modelos com tags

Até agora, analisamos o uso de strings de modelo para substituição de strings e para criar strings de várias linhas. Outro recurso importante é o modelo com tags. Os modelos com tags transformam uma string de modelo colocando um nome de função antes dela. Exemplo:

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

A semântica de uma string de modelo com tag é muito diferente da de uma normal. Na verdade, elas são um tipo especial de chamada de função: o exemplo acima "descompõe" em

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

Observe como o argumento (n + 1) corresponde à substituição que ocorre entre as entradas nª e (n + 1) na matriz de string. Isso pode ser útil para vários tipos de coisas, mas uma das mais simples é a conversão automática de qualquer variável interpolada.

Por exemplo, é possível escrever uma função de codificação de saída de HTML para que..

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

retorna uma string com as variáveis adequadas substituídas, mas com todos os caracteres não seguros do HTML substituídos. Vamos fazer isso. Nossa função de codificação de saída HTML vai receber dois argumentos: um nome de usuário e um comentário. Ambos podem conter caracteres inseguros do HTML (ou seja, ', ", <, > e &). Por exemplo, se o nome de usuário for "Domenic Denicola" e o comentário for "& é uma tag divertida", a saída será:

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

Nossa solução de modelo com tags pode ser escrita da seguinte forma:

// 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"

Outros usos possíveis incluem a codificação automática, formatação, localização e, em geral, substituições mais complexas:

// 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}`;

Resumo

As strings de modelo estão no Chrome 41 Beta+, na prévia técnica do IE, no Firefox 35+ e no io.js. Na prática, se você quiser usá-las na produção, elas têm suporte nos principais transpiladores ES6, incluindo Traceur e 6to5. Confira nosso exemplo de strings de modelo no repositório de exemplos do Chrome se quiser testá-las. Você também pode se interessar por Equivalentes do ES6 no ES5, que demonstra como alcançar algumas das vantagens das strings de modelo usando o ES5.

As strings de modelo trazem muitos recursos importantes para o JavaScript. Isso inclui maneiras melhores de fazer interpolação de string e expressão, strings de várias linhas e a capacidade de criar seus próprios DSLs.

Um dos recursos mais importantes que eles trazem são os modelos com tags, um recurso essencial para a criação de DSLs. Elas recebem as partes de uma string de modelo como argumentos, e você pode decidir como usar as strings e substituições para determinar a saída final da string.

Leitura adicional