Cómo obtener partes individuales de un número formateado para visualización personalizada
Divide los números formateados en componentes para aplicar estilos personalizados y construir interfaces complejas
Introducción
El método format() devuelve una cadena formateada completa como "$1,234.56" o "1.5M". Esto funciona bien para visualizaciones simples, pero no permite aplicar estilos diferentes a partes individuales. No puedes hacer que el símbolo de moneda esté en negrita, colorear la parte decimal de manera diferente o aplicar marcado personalizado a componentes específicos.
JavaScript proporciona el método formatToParts() para resolver este problema. En lugar de devolver una sola cadena, devuelve un array de objetos, cada uno representando una parte del número formateado. Cada parte tiene un tipo como currency, integer o decimal, y un valor como $, 1234 o .. Luego puedes procesar estas partes para aplicar estilos personalizados, construir diseños complejos o integrar números formateados en interfaces de usuario enriquecidas.
Por qué las cadenas formateadas son difíciles de personalizar
Cuando recibes una cadena formateada como "$1,234.56", no puedes identificar fácilmente dónde termina el símbolo de moneda y dónde comienza el número. Diferentes locales colocan los símbolos en diferentes posiciones. Algunos locales utilizan separadores diferentes. Analizar estas cadenas de manera fiable requiere una lógica compleja que duplica las reglas de formato ya implementadas en la API Intl.
Considera un panel que muestra cantidades monetarias con el símbolo de moneda en un color diferente. Con format(), necesitarías:
- Detectar qué caracteres son el símbolo de moneda
- Tener en cuenta los espacios entre el símbolo y el número
- Manejar diferentes posiciones de símbolos en distintos locales
- Analizar la cadena cuidadosamente para evitar romper el número
Este enfoque es frágil y propenso a errores. Cualquier cambio en las reglas de formato del locale rompe tu lógica de análisis.
El método formatToParts() elimina este problema proporcionando los componentes por separado. Recibes datos estructurados que te indican exactamente qué parte es cada una, independientemente del locale.
Uso de formatToParts para obtener componentes de números
El método formatToParts() funciona de manera idéntica a format() excepto por su valor de retorno. Creas un formateador con las mismas opciones, luego llamas a formatToParts() en lugar de format().
const formatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD"
});
const parts = formatter.formatToParts(1234.56);
console.log(parts);
Esto produce un array de objetos:
[
{ type: "currency", value: "$" },
{ type: "integer", value: "1" },
{ type: "group", value: "," },
{ type: "integer", value: "234" },
{ type: "decimal", value: "." },
{ type: "fraction", value: "56" }
]
Cada objeto contiene una propiedad type que identifica lo que representa la parte y una propiedad value que contiene la cadena real. Las partes aparecen en el mismo orden en que aparecerían en la salida formateada.
Puedes verificar esto uniendo todos los valores:
const formatted = parts.map(part => part.value).join("");
console.log(formatted);
// Salida: "$1,234.56"
Las partes concatenadas producen exactamente la misma salida que al llamar a format().
Entendiendo los tipos de partes
La propiedad type identifica cada componente. Diferentes opciones de formato producen diferentes tipos de partes.
Para el formato básico de números:
const formatter = new Intl.NumberFormat("en-US");
const parts = formatter.formatToParts(1234.56);
console.log(parts);
// [
// { type: "integer", value: "1" },
// { type: "group", value: "," },
// { type: "integer", value: "234" },
// { type: "decimal", value: "." },
// { type: "fraction", value: "56" }
// ]
El tipo integer representa la parte entera del número. Aparecen múltiples partes integer cuando los separadores de grupo dividen el número. El tipo group representa el separador de miles. El tipo decimal representa el punto decimal. El tipo fraction representa los dígitos después del decimal.
Para el formato de moneda:
const formatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "EUR"
});
const parts = formatter.formatToParts(1234.56);
console.log(parts);
// [
// { type: "currency", value: "€" },
// { type: "integer", value: "1" },
// { type: "group", value: "," },
// { type: "integer", value: "234" },
// { type: "decimal", value: "." },
// { type: "fraction", value: "56" }
// ]
El tipo currency aparece antes o después del número dependiendo de las convenciones de la localización.
Para porcentajes:
const formatter = new Intl.NumberFormat("en-US", {
style: "percent"
});
const parts = formatter.formatToParts(0.1234);
console.log(parts);
// [
// { type: "integer", value: "12" },
// { type: "percentSign", value: "%" }
// ]
El tipo percentSign representa el símbolo de porcentaje.
Para notación compacta:
const formatter = new Intl.NumberFormat("en-US", {
notation: "compact"
});
const parts = formatter.formatToParts(1500000);
console.log(parts);
// [
// { type: "integer", value: "1" },
// { type: "decimal", value: "." },
// { type: "fraction", value: "5" },
// { type: "compact", value: "M" }
// ]
El tipo compact representa el indicador de magnitud como K, M o B.
Aplicando estilos personalizados a partes de números
El caso de uso principal para formatToParts() es aplicar diferentes estilos a diferentes componentes. Puedes procesar el array de partes para envolver tipos específicos en elementos HTML.
Haciendo que el símbolo de moneda esté en negrita:
const formatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD"
});
const parts = formatter.formatToParts(1234.56);
const html = parts
.map(part => {
if (part.type === "currency") {
return `<strong>${part.value}</strong>`;
}
return part.value;
})
.join("");
console.log(html);
// Output: "<strong>$</strong>1,234.56"
Este enfoque funciona para cualquier lenguaje de marcado. Puedes generar HTML, JSX o cualquier otro formato procesando el array de partes.
Estilizando las partes decimales de manera diferente:
const formatter = new Intl.NumberFormat("en-US", {
minimumFractionDigits: 2
});
const parts = formatter.formatToParts(1234.5);
const html = parts
.map(part => {
if (part.type === "decimal" || part.type === "fraction") {
return `<span class="text-gray-500">${part.value}</span>`;
}
return part.value;
})
.join("");
console.log(html);
// Output: "1,234<span class="text-gray-500">.50</span>"
Este patrón es común en visualizaciones de precios donde la parte decimal aparece más pequeña o más clara.
Codificación por colores de números negativos
Las aplicaciones financieras a menudo muestran números negativos en rojo. Con formatToParts(), puedes detectar el signo menos y aplicar estilos en consecuencia.
const formatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD"
});
function formatWithColor(number) {
const parts = formatter.formatToParts(number);
const hasMinusSign = parts.some(part => part.type === "minusSign");
const html = parts
.map(part => part.value)
.join("");
if (hasMinusSign) {
return `<span class="text-red-600">${html}</span>`;
}
return html;
}
console.log(formatWithColor(-1234.56));
// Output: "<span class="text-red-600">-$1,234.56</span>"
console.log(formatWithColor(1234.56));
// Output: "$1,234.56"
Este enfoque detecta números negativos de manera confiable en todas las configuraciones regionales, incluso aquellas que utilizan diferentes símbolos o posiciones para los indicadores negativos.
Creación de visualizaciones de números personalizadas con múltiples estilos
Las interfaces complejas a menudo combinan múltiples reglas de estilo. Puedes aplicar diferentes clases o elementos a diferentes tipos de partes simultáneamente.
const formatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD"
});
function formatCurrency(number) {
const parts = formatter.formatToParts(number);
return parts
.map(part => {
switch (part.type) {
case "currency":
return `<span class="currency-symbol">${part.value}</span>`;
case "integer":
return `<span class="integer">${part.value}</span>`;
case "group":
return `<span class="group">${part.value}</span>`;
case "decimal":
return `<span class="decimal">${part.value}</span>`;
case "fraction":
return `<span class="fraction">${part.value}</span>`;
case "minusSign":
return `<span class="minus">${part.value}</span>`;
default:
return part.value;
}
})
.join("");
}
console.log(formatCurrency(1234.56));
// Output: "<span class="currency-symbol">$</span><span class="integer">1</span><span class="group">,</span><span class="integer">234</span><span class="decimal">.</span><span class="fraction">56</span>"
Este control granular permite un estilo preciso para cada componente. Puedes usar CSS para aplicar estilos diferentes a cada clase.
Todos los tipos de partes disponibles
La propiedad type puede tener estos valores dependiendo de las opciones de formato utilizadas:
integer: Dígitos de números enterosfraction: Dígitos decimalesdecimal: Separador decimalgroup: Separador de milescurrency: Símbolo de monedaliteral: Espaciado u otro texto literal añadido por el formatopercentSign: Símbolo de porcentajeminusSign: Indicador de número negativoplusSign: Indicador de número positivo (cuandosignDisplayestá configurado)unit: Cadena de unidad para formato de unidadescompact: Indicador de magnitud en notación compacta (K, M, B)exponentInteger: Valor del exponente en notación científicaexponentMinusSign: Signo negativo en el exponenteexponentSeparator: Símbolo que separa la mantisa del exponenteinfinity: Representación de infinitonan: Representación de no-es-un-númerounknown: Tokens no reconocidos
No todas las opciones de formato producen todos los tipos de partes. Las partes que recibes dependen del valor numérico y de la configuración del formateador.
La notación científica produce partes relacionadas con el exponente:
const formatter = new Intl.NumberFormat("en-US", {
notation: "scientific"
});
const parts = formatter.formatToParts(1234);
console.log(parts);
// [
// { type: "integer", value: "1" },
// { type: "decimal", value: "." },
// { type: "fraction", value: "234" },
// { type: "exponentSeparator", value: "E" },
// { type: "exponentInteger", value: "3" }
// ]
Los valores especiales producen tipos de partes específicos:
const formatter = new Intl.NumberFormat("en-US");
console.log(formatter.formatToParts(Infinity));
// [{ type: "infinity", value: "∞" }]
console.log(formatter.formatToParts(NaN));
// [{ type: "nan", value: "NaN" }]
Creación de visualizaciones de números accesibles
Puedes usar formatToParts() para añadir atributos de accesibilidad a números formateados. Esto ayuda a los lectores de pantalla a anunciar los valores correctamente.
const formatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD"
});
function formatAccessibleCurrency(number) {
const parts = formatter.formatToParts(number);
const formatted = parts.map(part => part.value).join("");
return `<span aria-label="${number} US dollars">${formatted}</span>`;
}
console.log(formatAccessibleCurrency(1234.56));
// Output: "<span aria-label="1234.56 US dollars">$1,234.56</span>"
Esto asegura que los lectores de pantalla anuncien tanto el valor de visualización formateado como el valor numérico subyacente con el contexto adecuado.
Resaltado de rangos específicos de números
Algunas aplicaciones resaltan números que caen dentro de ciertos rangos. Con formatToParts(), puedes aplicar estilos basados en el valor mientras mantienes un formato adecuado.
const formatter = new Intl.NumberFormat("en-US");
function formatWithThreshold(number, threshold) {
const parts = formatter.formatToParts(number);
const formatted = parts.map(part => part.value).join("");
if (number >= threshold) {
return `<span class="text-green-600 font-bold">${formatted}</span>`;
}
return formatted;
}
console.log(formatWithThreshold(1500, 1000));
// Output: "<span class="text-green-600 font-bold">1,500</span>"
console.log(formatWithThreshold(500, 1000));
// Output: "500"
El número recibe el formato adecuado para la configuración regional mientras se aplica el estilo condicional basado en la lógica de negocio.
Cuándo usar formatToParts versus format
Usa format() cuando necesites una cadena formateada simple sin ninguna personalización. Este es el caso común para la mayoría de las visualizaciones de números.
Usa formatToParts() cuando necesites:
- Aplicar diferentes estilos a diferentes partes del número
- Construir HTML o JSX con números formateados
- Añadir atributos o metadatos a componentes específicos
- Integrar números formateados en diseños complejos
- Procesar la salida formateada programáticamente
El método formatToParts() tiene un poco más de sobrecarga que format() porque crea un array de objetos en lugar de una sola cadena. Esta diferencia es insignificante para aplicaciones típicas, pero si formateas miles de números por segundo, format() tiene mejor rendimiento.
Para la mayoría de las aplicaciones, elige según tus necesidades de estilo en lugar de preocupaciones de rendimiento. Si no necesitas personalizar la salida, usa format(). Si necesitas estilos personalizados o marcado, usa formatToParts().
Cómo las partes preservan el formato específico del idioma
El array de partes mantiene automáticamente las reglas de formato específicas del idioma. Los diferentes idiomas colocan los símbolos en diferentes posiciones y utilizan diferentes separadores, pero formatToParts() maneja estas diferencias.
const usdFormatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD"
});
console.log(usdFormatter.formatToParts(1234.56));
// [
// { type: "currency", value: "$" },
// { type: "integer", value: "1" },
// { type: "group", value: "," },
// { type: "integer", value: "234" },
// { type: "decimal", value: "." },
// { type: "fraction", value: "56" }
// ]
const eurFormatter = new Intl.NumberFormat("de-DE", {
style: "currency",
currency: "EUR"
});
console.log(eurFormatter.formatToParts(1234.56));
// [
// { type: "integer", value: "1" },
// { type: "group", value: "." },
// { type: "integer", value: "234" },
// { type: "decimal", value: "," },
// { type: "fraction", value: "56" },
// { type: "literal", value: " " },
// { type: "currency", value: "€" }
// ]
El formato alemán coloca la moneda después del número con un espacio. El separador de grupos es un punto, y el separador decimal es una coma. Tu código de estilo procesa el array de partes de la misma manera independientemente del idioma, y el formato se adapta automáticamente.
El tipo literal representa cualquier espacio o texto insertado por el formateador que no encaja en otras categorías. En el formato de moneda alemán, representa el espacio entre el número y el símbolo de moneda.
Combinando formatToParts con componentes de frameworks
Los frameworks modernos como React pueden utilizar formatToParts() para construir componentes de manera eficiente.
function CurrencyDisplay({ value, locale, currency }) {
const formatter = new Intl.NumberFormat(locale, {
style: "currency",
currency: currency
});
const parts = formatter.formatToParts(value);
return (
<span className="currency-display">
{parts.map((part, index) => {
if (part.type === "currency") {
return <strong key={index}>{part.value}</strong>;
}
if (part.type === "fraction" || part.type === "decimal") {
return <span key={index} className="text-sm text-gray-500">{part.value}</span>;
}
return <span key={index}>{part.value}</span>;
})}
</span>
);
}
Este componente aplica diferentes estilos a diferentes partes mientras mantiene el formato adecuado para cualquier idioma y moneda.