Al igual que en SASS, en styled-components también podemos usar funciones y mixins como herramientas que nos ayuden a realizar tareas con el mismo objetivo que con el pre-procesador.

Para estas herramientas he creado las carpetas styled-components-utils/mixinsstyled-components-utils/functions dentro del proyecto. En cada una de ellas he creado un fichero index.js que me sirve de bootstrap para los mixins y funciones.

De esta forma, en el caso que el número de mixins y funciones comience a ser muy elevado, evito que los ficheros crezcan de forma desmesurada, haciendo complicado la búsqueda y lectura.

Por lo demás, el funcionamiento de estas utilidades para crear estilos, es similar a otros pre-procesadores CSS, en este caso, utilizando una sintaxis JavaScript de lo más moderna. Vamos a ver.

Mixins

a) Sin parámetros

Los mixins tienen como objetivo devolvernos una serie de propiedades y valores CSS reutilizables para nuestros componentes. Para ello, un ejemplo sencillo y común de la definición de un mixin:

const clearfix = `
  content: "";
  display: block;
  clear: both;
  font-size: 0;
  line-height: 0;
`;

Para usarlo dentro de por ejemplo nuestro styled.js del componente Paragraph basta con hacer lo siguiente:


/** Importar la librería styled-components **/
import styled from 'styled-components';
// Importamos el mixin clearfix
import { clearfix } from '../../styled-components-utils/mixins';

/** Indicar el estilo para un elemento de tipo párrafo **/
const StyleParagraph = styled.p`
    font-size: 150%;
    font-family: Arial;
    text-align: center;
    background-color: #EFEFEF;
    padding: 2rem;
    max-width: 20rem;
    margin: auto;
    text-transform: lowercase;
    font-variant: small-caps;
    display: flex;
    flex-flow: column;
    align-items: center;
    align-content: flex-start;
    justify-content: flex-start;
    
    // Usamos el mixin clearfix dentro del
    // pseudoelemento after de StyledPromo
    &:after {
        ${clearfix};
    }
`;

/** Exportar los elementos del fichero **/
export {StyleParagraph};

Se puede apreciar el uso de template strings o plantillas de cadena (en español). Lo que hago es incluir el contenido del mixin dentro del pseudoelemento after del elemento que forma parte del componente. Sin la necesidad de utilizar la instrucción return, la llamada al mixin nos devuelve el contenido de este.

b) Con parámetros

Existe la posibilidad de crear mixins con parámetros. Para ello creo un nuevo mixin, con un objetivo similar al clearfix, pero que pueda recibir un parámetro:

const clearfixH = (altolinea = 0) => 
`
    content: "";
    display: block;
    clear: both;
    font-size: 0;
    line-height: ${altolinea};
`;

En la definición del mixin asigno un valor por defecto al parámetro. De esta forma la llamada al mixin podría ir sin argumentos o con el argumento altolinea. Esto de proporcionar un valor por defecto en la definición es posible gracias a las nuevas especificaciones del lenguaje JavaScript.

La llamada a este nuevo mixin es algo diferente al anterior:

/** Importar la librería styled-components **/
import styled from 'styled-components';
// Importamos el mixin clearfix
import { clearfixH } from '../../styled-components-utils/mixins';

/** Indicar el estilo para un elemento de tipo párrafo **/
const StyleParagraph = styled.p`
    font-size: 150%;
    font-family: Arial;
    text-align: center;
    background-color: #EFEFEF;
    padding: 2rem;
    max-width: 20rem;
    margin: auto;
    text-transform: lowercase;
    font-variant: small-caps;
    display: flex;
    flex-flow: column;
    align-items: center;
    align-content: flex-start;
    justify-content: flex-start;

    // Usamos el mixin clearfix dentro del
    // pseudoelemento after de StyledPromo
    &:after {
        ${clearfixH()};
    }
`;

/** Exportar los elementos del fichero **/
export {StyleParagraph};

Observa que en este caso la llamada se hace con paréntesis al final. Es similar a la llamada de un método o función en JavaScript. Esto es debido a que en este caso estoy haciendo uso de las llamadas funciones flecha. Las arrow functions (en inglés) es un nuevo camino para la definición de funciones, aunque tiene unas características diferentes a la nomenclatura tradicional.

Es posible definir los mixins con una sintáxis más clásica de JavaScript, y este seguirá funcionando. Veamos dos ejemplos:

const clearfixH = function (lineHeight = 0) {
  `
    content: "";
    display: block;
    clear: both;
    font-size: 0;
    line-height: ${lineHeight};
  `;
}
function clearfixH(lineHeight = 0) {
  `
    content: "";
    display: block;
    clear: both;
    font-size: 0;
    line-height: ${lineHeight};
  `;
}

Volviendo al tema que nos ocupa, ahora que el mixin es capaz de recibir argumentos, debemos añadir los paréntesis en la llamada, en caso contrario nos devolverá un error. En el ejemplo no he pasado ningún argumento en la llamada, lo que significa que devolverá los valores declarados en el mixin con el valor por defecto en el line-height.

Viendo que puede ser bastante lio la llamada a mixins con o sin parámetros, pienso que es buena práctica a seguir que todos los mixins sean definidos, independientemente de si reciben o no parámetros, con alguna de las tres últimas formas planteadas. De esa forma, la llamada siempre se hará con paréntesis, aunque no tenga argumentos. Además, los mixins tienen una alta probabilidad de mutar a tener parámetros en el futuro. Imagina tener que cambiar la llamada de estos en los diferentes elementos donde se haya usado. ¡¡Puede ser un coñazo si éste ha sido empleado en muchos elementos!!.

Funciones

En el caso de las funciones, el objetivo es devolver un valor tras realizar la ejecución de, por ejemplo, un cálculo. Usualmente ejecutará una serie de instrucciones para obtener el valor que necesitamos a partir de uno o varios argumentos o parámetros pasados en la llamada. Esto, implícitamente, nos lleva a pensar que, en la mayoría de los casos, su uso será con argumentos o parámetros. ¿Podría existir algún caso en el que no usemos parámetros? Seguro, pero creo que en la mayoría de los casos eso sería un mixin.

Veamos un ejemplo. Quiero convertir pixeles en rem. Para ello creo la siguiente función:

const rem = (unit) => {
  if (typeof unit !== 'number') {
    unit = parseInt(unit, 10);
  }
  return `${unit * 0.0625}rem`;
}

export { rem };

Esta función recibe un parámetro con una unidad, por ejemplo '6px', que convertirá en rem multiplicando el valor pasado por los 0.0625. La instrucción return hace el cálculo y devuelve un string con el valor.

NOTA: 1px = 0.0625rem si el documento tiene un tamaño de fuente definido de 16px. Este valor de 16px se suele usar ya que es el valor por defecto declarado por el navegador, y todos sabemos que ¡¡nadie cambia este valor!!.

Veamos como usarlo en nuestro componente Paragraph:

/** Importar la librería styled-components **/
import styled from 'styled-components';
import { rem } from '../../styled-components-utils/functions';
import { clearfix } from '../../styled-components-utils/mixins';

/** Indicar el estilo para un elemento de tipo párrafo **/
const StyleParagraph = styled.p`
    font-size: 150%;
    font-family: Arial;
    text-align: center;
    background-color: #EFEFEF;
    padding: 2rem;
    max-width: 20rem;
    margin: auto;
    text-transform: lowercase;
    font-variant: small-caps;
    display: flex;
    flex-flow: column;
    align-items: center;
    align-content: flex-start;
    justify-content: flex-start;

    font-size: ${rem('20px')};

    &:after {
        ${clearfix()};
    }
`;

/** Exportar los elementos del fichero **/
export { StyleParagraph };

Difrencias entre funciones y mixins

Se puede observar que la sintaxis de llamada es igual en las funciones que en los mixins. Ambos recursos son funciones de JavaScript. La diferencia radica en el objetivo que debería de tener cada recurso.

Como he comentado más arriba, el objetivo de los mixins es usar una seria de propiedades y valores CSS susceptibles de ser usados en varios elementos. Mientras que el objetivo de las funciones es realizar, por ejemplo, un cálculo para obtener un valor para ser asignado a una propiedad CSS. ¿Quiere decir esto que no puedo hacer cálculos en los mixins? La respuesta es no, puedes hacerlos. Iría más allá. Podría usar la llamada a una función desde un mixin para realizar dicho cálculo. Este me valdría para el valor de una o varias propiedades.

Otra diferencia que puede observarse entre los mixins y las funciones es que en el caso de los mixin nos devuelve un número determinado de valores. Para ello hago uso de los template string, muy similar a cuando escribes los estilos para un elemento de un componente. Por otro lado en la función he escrito una serie de instrucciones para obtener un valor. En el caso concreto de esta función también devuelve un template string, aunque esto podría haberlo resuelto en otro lugar. Dependerá del objetivo de la función. En este caso si parece acertado hacerlo así.

Otras consideraciones para funciones y mixins

Se debe tener en cuenta que para poder usar los mixins y las funciones, hay que dejarlo preparado con la instrucción export en cada mixin o función. Esto ya lo usé en styled.js e index.js. Veamos ejemplos de cada uno de los ficheros:

const rem = (unit) => {
  if (typeof unit !== 'number') {
    unit = parseInt(unit, 10);
  }
  return `${unit * 0.0625}rem`;
}

export { rem };

Es posible añadirlo en la misma línea, indicando directamente que función o mixin es exportable como podemos ver a continuación:

export const rem = (unit) => {
  if (typeof unit !== 'number') {
    unit = parseInt(unit, 10);
  }
  return `${unit * 0.0625}rem`;
}

Esto es una cuestión de gustos, aunque personalmente prefiero añadirlo al final del fichero, indicando que funciones o mixins es posible usar fuera del contexto del fichero. Veamos un ejemplo:

const borders = `
    border: 1px solid #AAA;
`;

const nospace = `
    margin: 0;
    padding: 0;
    line-height: 0;
    box-sizing: border-box;
`;

const clearfix = `
    content: "";
    display: block;
    clear: both;
    font-size: 0;
    line-height: 0;
`;

const colorfondo = (color = 'tomato') =>
  `
    background: ${color};
`;

export { borders, clearfix, nospace, colorfondo };

Se puede echar un vistazo a todo esto en mi repositorio público de gitlab.