Controlar el alcance es algo que probablemente no tiende a considerar cuando trabaja con CSS y Sass. Hemos tenido acceso al ampersand (&
) desde hace bastante tiempo, lo que nos da un nivel de alcance, pero es fácil que pierda su utilidad cuando estás anidado con bastante profundidad. El &
puede enviarnos por un camino ventoso y fatal cuando perdemos por completo la noción de lo que significa y podemos ser responsables de una gran hinchazón.
Mientras tanto, en JavaScript, podemos controlar el alcance de this
lanzándolo como una variable en el punto donde significa lo que queremos que sea. Esto suele ocurrir al comienzo de un método u objeto:
function foo() {
let self = this;
return function() {
// Self = foo(), even int his closure
return self;
}
}
// We need to instantiate foo() or its 'this' will be the window
let bar = new foo();
Ahora que tenemos self
en el contexto anterior, siempre tenemos una referencia a foo()
independientemente de dónde nos encontremos dentro de la función. Esto es realmente útil cuando terminamos en setTimeout
escenarios, cierres o manejadores de eventos.
Con suerte, un ejemplo rápido de un evento ayudará a ilustrar mi punto.
Usando este marcado:
<div class="component">
<div class="component__child-element"></div>
</div>
<div class="component">
<div class="component__child-element"></div>
</div>
Podemos agregar este JavaScript:
function foo() {
// Load up all component child elements
let childElements = [...document.querySelectorAll('.component__child-element')];
// Loop each element and add an event listener
childElements.map(element => {
element.addEventListener('click', function(evt) {
// Log what `this` currently is
console.log(this);
});
});
}
// Create a new instance of foo
let bar = new foo();
En esa muestra de código, si hace clic en un component__child-element
con tu consola abierta, this
se informará a sí mismo como el objetivo del evento, que resulta ser el elemento en el que hicimos clic. Esto no es ideal si pensaba que haría referencia foo()
.
Ahora, si ejecutamos la misma muestra con self
en lugar de this
en el controlador de eventos, la consola informará una instancia de foo()
en lugar de:
function foo() {
// Control scope of this by storing it
let self = this;
// Load up all component child elements
let childElements = [...document.querySelectorAll('.component__child-element')];
// Loop each element and add an event listener
childElements.map(element => {
element.addEventListener('click', function(evt) {
// Self will report itself as `foo()`
console.log(self);
});
});
}
// Create a new instance of foo
let bar = new foo();
Entonces, ¿cómo se relaciona esto con Sass?
Me alegro de que me quedaras ahí, porque ese ejemplo de JavaScript fue un manual de lo que vamos a ver con Sass.
Primero, comencemos con el mismo marcado y algunos estilos básicos.
<div class="component">
<div class="component__child-element"></div>
</div>
<div class="component">
<div class="component__child-element"></div>
</div>
.component {
display: block;
max-width: 30rem;
min-height: 30rem;
margin: 5rem auto;
background: rebeccapurple;
position: relative;
border: 1px dashed rebeccapurple;
&__child-element {
display: block;
width: 15rem;
height: 15rem;
border-radius: 50%;
position: absolute;
top: 50%;
left: 50%;
transform: translate3d(-50%, -50%, 0);
background: white;
}
}
Esto nos da un bonito cuadrado morado con un círculo blanco dentro. Nada innovador, pero útil para este ejemplo.
Vea el Pen Square con Circle de Geoff Graham (@geoffgraham) en CodePen.
Probablemente notará que estamos usando la sintaxis BEM, así que sigamos adelante y creemos un modificador.
Queremos una versión invertida de .component
que tiene un fondo blanco con un círculo morado. Entonces, sigamos adelante y abordémoslo como probablemente pensamos hacerlo de inmediato incluyéndolo en el mismo conjunto de reglas anidado:
.component {
// Other styles redacted for brevity
// The reversed modifier flips the colors around
&--reversed {
background: white;
border-color: lightgray;
&__child-element {
background: rebeccapurple;
}
}
}
Vea el Pen Square con Circle de Geoff Graham (@geoffgraham) en CodePen.
Espera, ¿por qué esto no funciona? El problema es que el &
tiene un alcance de .component--reversed
, entonces &__child-element
compila a .component--reversed__child-element
, que no existe en el marcado.
$self
¡al rescate!
Al igual que en el JavaScript que vimos antes, vamos a convertir nuestro alcance inicial en una variable llamada $self
. Puedes hacerlo así:
.component {
$self: &;
}
Eso significa que dondequiera que usemos $self
en nuestro componente, se compilará para .component
.
Así que refactoricemos ese modificador invertido, para que realmente funcione:
.component {
$self: &; // Hey look, it's our new best friend!
display: block;
max-width: 30rem;
min-height: 30rem;
// Other styles redacted
&--reversed {
background: white;
border-color: lightgray;
// Here, we use $self to get the correct scope
#{ $self }__child-element {
background: rebeccapurple;
}
}
}
El CSS compilado para ese elemento ahora es .component--reversed .component__child-element
que, por supuesto, existe y convierte con éxito el círculo interior en violeta:
Vea el ejemplo de Pen ‘Uso de Sass para controlar el alcance con nombres BEM’ de Andy Bell (@hankchizljaw) en CodePen.
Exploración adicional
Una cosa que me gusta hacer en mis componentes es hacer referencia al modificador de un padre dentro del elemento en sí, en lugar de hacer referencia al elemento dentro del modificador como hicimos antes. Esto es para mantener mis estilos agrupados por elemento. Por la misma razón, también incluiré consultas de medios dentro de los elementos.
Termino con un código que se ve así:
// We're still within .component where $self has been declared.
&__child-element {
display: block;
width: 15rem;
height: 15rem;
border-radius: 50%;
position: absolute;
top: 50%;
left: 50%;
transform: translate3d(-50%, -50%, 0);
background: white;
// Flip the color when the parent is reversed
#{ $self }--reversed & {
background: rebeccapurple;
}
}
Tener acceso a $self
ya que el contexto raíz nos da la flexibilidad de abordar nuestros modificadores de esa manera con facilidad. Porque $self
es .component
y &
es .component__element
, agregando el --reversed
porción nos genera .component--reversed .component__element
que es exactamente lo que queríamos.
Vea el ejemplo de Pen ‘Uso de Sass para controlar el alcance con nombres BEM’ de Andy Bell (@hankchizljaw) en CodePen.
Un ejemplo más avanzado
Ahora vamos a sacar el barco y escribir algo interesante. Vamos a diseñar tres cuadrículas separadas. Utilizando $self
, Voy a cambiar el color de los elementos de la cuadrícula usando un selector hermano dentro de un elemento de la cuadrícula en sí.
Vea el ejemplo de Pen ‘Uso de Sass para controlar el alcance con nombres BEM’ de Andy Bell (@hankchizljaw) en CodePen.
Veamos el Sass que afecta el color:
&__item {
// Item styles redacted for brevity
// For each direct `.grid sibling`, make this item rebeccapurple
#{ $self } + #{ $self } & {
background: rebeccapurple;
}
}
Lo que compila ese selector es .grid + .grid .grid__item
. La combinación de $self
y &
nos permite generar eso dentro del contexto del elemento que es nuestro &
. Esto es increíblemente poderoso para mantener algún tipo de orden dentro de bases de código complejas.
Terminando
Nos hemos inspirado en JavaScript para controlar el alcance en nuestros componentes BEM con Sass usando este pequeño fragmento en la raíz: $self: &
. Con el control de alcance, ganamos flexibilidad para escribir CSS limpio, organizado y controlado por BEM cuando estamos profundamente en un modificador o elemento secundario.
Espero que este pequeño consejo te ayude a mejorar tu Sass. Si lo hace, ponte en contacto conmigo en Twitter o deje un comentario a continuación. Será genial ver cómo te ha ayudado ganar el control del alcance con Sass.