Cómo obtener literales con strings de plantilla de ES6

Addy Osmani
Addy Osmani

Las cadenas en JavaScript han sido históricamente limitadas y carecen de las capacidades que se podrían esperar de lenguajes como Python o Ruby. Las cadenas de plantilla de ES6 (disponibles en Chrome 41 y versiones posteriores) cambian esto de forma fundamental. Presentan una forma de definir cadenas con lenguajes específicos de dominio (DSL), lo que brinda mejores:

  • Interpolación de cadenas
  • Expresiones incorporadas
  • Cadenas de varias líneas sin hacks
  • Formato de cadenas
  • Etiquetado de cadenas para escapar de HTML de forma segura, localización y mucho más.

En lugar de agregar otra función a las cadenas tal como las conocemos hoy, las cadenas de plantillas presentan una forma completamente diferente de resolver estos problemas.

Sintaxis

Las cadenas de plantillas usan acentos graves (``) en lugar de comillas simples o dobles a las que estamos acostumbrados con las cadenas normales. Por lo tanto, una cadena de plantilla se podría escribir de la siguiente manera:

var greeting = `Yo World!`;

Hasta ahora, las cadenas de plantillas no nos han brindado nada más que las cadenas normales. Cambiemos eso.

Sustitución de cadenas

Uno de sus primeros beneficios reales es la sustitución de cadenas. La sustitución nos permite tomar cualquier expresión válida de JavaScript (incluida, por ejemplo, la adición de variables) y, dentro de un literal de plantilla, el resultado se mostrará como parte de la misma cadena.

Las cadenas de plantillas pueden contener marcadores de posición para la sustitución de cadenas con la sintaxis ${ }, como se muestra a continuación:

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

// => "Yo, Brendan!"

Como todas las sustituciones de cadenas en las cadenas de plantillas son expresiones de JavaScript, podemos sustituir mucho más que los nombres de las variables. Por ejemplo, a continuación, podemos usar la interpolación de expresiones para incorporar algunas matemáticas intercaladas legibles:

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.

También son muy útiles para las funciones dentro de expresiones:

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

${} funciona bien con cualquier tipo de expresión, incluidas las expresiones de miembros y las llamadas a métodos:

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

Si necesitas acentos graves dentro de la cadena, puedes escaparlos con el carácter de barra inversa \ de la siguiente manera:

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

Cadenas de varias líneas

Las cadenas de varias líneas en JavaScript requieren soluciones alternativas de hackeo desde hace algún tiempo. Las soluciones actuales para ellos requieren que las cadenas existan en una sola línea o se dividan en cadenas de varias líneas con una \ (barra invertida) antes de cada línea nueva. Por ejemplo:

var greeting = "Yo \
World";

Si bien esto debería funcionar bien en la mayoría de los motores de JavaScript modernos, el comportamiento en sí sigue siendo un poco hack. También se puede usar la concatenación de cadenas para simular la compatibilidad con varias líneas, pero esto también deja algo que desear:

var greeting = "Yo " +
"World";

Las cadenas de plantillas simplifican mucho las cadenas de varias líneas. Simplemente incluye líneas nuevas donde sea necesario y ¡voilà! Por ejemplo:

Cualquier espacio en blanco dentro de la sintaxis de acentos graves también se considerará parte de la cadena.

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

Plantillas etiquetadas

Hasta ahora, analizamos el uso de cadenas de plantilla para la sustitución de cadenas y para crear cadenas de varias líneas. Otra función potente que ofrecen son las plantillas etiquetadas. Las plantillas etiquetadas transforman una cadena de plantillas colocando un nombre de función antes de la cadena de plantillas. Por ejemplo:

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

La semántica de una cadena de plantilla etiquetada es muy diferente de la de una normal. En esencia, son un tipo especial de llamada a función: lo anterior se "expande" en

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

Observa cómo el argumento (n + 1) corresponde a la sustitución que se produce entre la entrada n y la (n + 1) en el array de cadenas. Esto puede ser útil para todo tipo de tareas, pero una de las más sencillas es la evasión automática de cualquier variable interpolada.

Por ejemplo, puedes escribir una función de escape de HTML de modo que…

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

Muestra una cadena con las variables apropiadas reemplazadas, pero con todos los caracteres no seguros para HTML reemplazados. Hagámoslo. Nuestra función de escape de HTML tomará dos argumentos: un nombre de usuario y un comentario. Ambos pueden contener caracteres no seguros de HTML (es decir, ", ", <, > y &). Por ejemplo, si el nombre de usuario es "Domenic Denicola" y el comentario es "& is a fun tag", deberíamos mostrar lo siguiente:

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

Por lo tanto, nuestra solución de plantilla etiquetada podría escribirse de la siguiente manera:

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

Otros usos posibles incluyen la codificación automática, el formato, la localización y, en general, las sustituciones más complejas:

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

Resumen

Las cadenas de plantillas están en Chrome 41 beta y versiones posteriores, la Versión preliminar de tecnología de IE, Firefox 35 y io.js. En términos prácticos, si quieres usarlos en producción hoy, son compatibles con los transpiladores ES6 principales, incluidos Traceur y 6to5. Si quieres probarlas, consulta nuestro ejemplo de cadenas de plantillas en el repositorio de muestras de Chrome. También te puede interesar ES6 Equivalents in ES5, que muestra cómo lograr algunas de las cadenas de plantillas de expansión que ofrece ES5 en la actualidad.

Las cadenas de plantillas aportan muchas funciones importantes a JavaScript. Estos incluyen mejores formas de realizar la interpolación de cadenas y expresiones, cadenas de varias líneas y la capacidad de crear tus propias DSL.

Una de las funciones más significativas que ofrecen son las plantillas etiquetadas, una función fundamental para la creación de esas DSL. Reciben las partes de una cadena de plantilla como argumentos y, luego, puedes decidir cómo usar las cadenas y las sustituciones para determinar el resultado final de la cadena.

Lecturas adicionales