
Estaba trabajando en una gran aplicación React para una startup, y además de solo querer algunas buenas estrategias para mantener nuestros estilos organizados, quería darle una oportunidad a todo esto del “modo oscuro”. Con el enorme ecosistema que rodea a React, podría pensar que habría una solución para los temas de estilo, pero una pequeña búsqueda en la web muestra que realmente no es el caso.
Hay muchas opciones diferentes, pero muchas de ellas se relacionan con estrategias CSS muy específicas, como el uso de módulos CSS, alguna forma de CSS en JS, etc. También encontré herramientas específicas para ciertos marcos, como Gatsby, pero no un proyecto React genérico. Lo que estaba buscando era un sistema básico que fuera fácil de configurar y trabajar sin tener que pasar por un montón de obstáculos; algo rápido, algo fácil de incorporar rápidamente a todo un equipo de desarrolladores front-end y full-stack.
La solución existente que más me gustó se centró en el uso Variables CSS y atributos de datos, que se encuentra en esta respuesta de StackOverflow. Pero eso también se basó en algunos useRef
cosas que se sentían hack-y. Como dicen en todos los infomerciales de la historia, ¡tiene que haber una mejor manera!
Afortunadamente, lo hay. Combinando esa estrategia de variable CSS general con la hermosa useLocalStorage
hook, tenemos un sistema de tematización potente y fácil de usar. Voy a explicar cómo configurar esto y ejecutarlo, comenzando desde una nueva aplicación React. Y si te quedas hasta el final, también te muestro cómo integrarlo con react-scoped-css, que es lo que hace que esta sea mi forma absolutamente preferida de trabajar con CSS en React.
Configuración del proyecto
Tomemos esto en un muy buen punto de partida: el principio.
Esta guía asume una familiaridad básica con CSS, JavaScript y React.
Primero, asegúrese de tener instalada una versión reciente de Node y npm. Luego navegue a la carpeta en la que desee que viva su proyecto, ejecute git bash
allí (o su herramienta de línea de comando preferida), luego ejecute:
npx create-react-app easy-react-themes --template typescript
Intercambiar easy-react-themes
con el nombre de su proyecto y no dude en dejar el --template typescript
si prefiere trabajar en JavaScript. Me gusta TypeScript, pero realmente no hace ninguna diferencia para esta guía, aparte de los archivos que terminan en .ts / .tsx vs .js / .jsx.
Ahora abriremos nuestro nuevo proyecto en un editor de código. Estoy usando VS Code para este ejemplo, y si usted también lo está, puede ejecutar estos comandos:
cd easy-react-themes
code .
No hay mucho que ver todavía, ¡pero cambiaremos eso!
Corriendo npm start
a continuación, inicia su servidor de desarrollo y genera esto en una nueva ventana del navegador:
Y, finalmente, continúe e instale el paquete use-local-storage con:
npm i use-local-storage
¡Y eso es todo para la configuración inicial del proyecto!
Configuración de código
Abre el App.tsx
archivo y deshacerse de las cosas que no necesitamos.
Queremos pasar de esto …
…a esto.
Eliminar todo el contenido en App.css
:
¡Woot! ¡Ahora creemos nuestros temas! Abre el index.css
archivo y agréguele esto:
:root {
--background: white;
--text-primary: black;
--text-secondary: royalblue;
--accent: purple;
}
[data-theme="dark"] {
--background: black;
--text-primary: white;
--text-secondary: grey;
--accent: darkred;
}
Esto es lo que tenemos hasta ahora:
¿Ves lo que acabamos de hacer allí? Si no está familiarizado con las propiedades personalizadas de CSS (también conocidas como variables CSS), nos permiten definir un valor para usar en otras partes de nuestras hojas de estilo, con el patrón --key: value
. En este caso, solo definimos algunos colores y los aplicamos a la :root
para que se puedan usar en cualquier otro lugar que los necesitemos en todo el proyecto React.
La segunda parte, comenzando con [data-theme="dark"]
, es donde las cosas se ponen interesantes. HTML (y JSX, que estamos usando para crear HTML en React) nos permite establecer propiedades completamente arbitrarias para nuestros elementos HTML con el data-*
atributo. En este caso, estamos dando la más externa <div>
elemento de nuestra aplicación a data-theme
atributo y alternar su valor entre light
y dark
. Cuando es dark
, el CSS[data-theme="dark"]
sección anula las variables que definimos en el :root
, por lo que también se alterna cualquier estilo que se base en esas variables.
Pongamos eso en práctica. De nuevo en App.tsx
, demos a React una forma de rastrear el estado del tema. Normalmente usamos algo como useState
para el estado local, o Redux para la gestión del estado global, pero también queremos que la selección del tema del usuario se quede si abandona nuestra aplicación y regresa más tarde. Si bien podríamos usar Redux y redux-persist, eso es demasiado para nuestras necesidades.
En cambio, estamos usando el useLocalStorage
gancho que instalamos anteriormente. Nos brinda una forma de almacenar cosas en el almacenamiento local, como era de esperar, pero como un gancho de React, mantiene un conocimiento completo de lo que está haciendo con localStorage
, facilitando nuestras vidas.
Algunos de ustedes podrían estar pensando “Oh no, ¿qué pasa si la página se procesa antes de que nuestro JavaScript se registre con localStorage
y obtenemos el temido “destello de tema equivocado” Pero no tiene que preocuparse por eso aquí, ya que nuestra aplicación React está completamente renderizada del lado del cliente; el archivo HTML inicial es básicamente un esqueleto con un solo <div>
al que React adjunta la aplicación. Todos los elementos HTML finales son generados por JavaScript después de verificar localStorage
.
Entonces, primero, importe el gancho en la parte superior de App.tsx
con:
import useLocalStorage from 'use-local-storage'
Entonces, dentro de nuestro App
componente, lo usamos con:
const defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const [theme, setTheme] = useLocalStorage('theme', defaultDark ? 'dark' : 'light');
Esto hace algunas cosas por nosotros. Primero, verificamos si el usuario ha establecido una preferencia de tema en la configuración de su navegador. Entonces estamos creando un estado theme
variable que está ligada a localStorage
y el setTheme
función para actualizar theme
. useLocalStorage
agrega un key:value
emparejar a localStorage
si aún no existe, que por defecto es theme: "light"
, a menos que nuestro matchMedia
el cheque vuelve como true
, en cuyo caso es theme: "dark"
. De esa manera, manejamos con elegancia las dos posibilidades de mantener la configuración del tema para un usuario que regresa o respetar la configuración de su navegador de forma predeterminada si estamos trabajando con nuevos usuarios.
A continuación, agregamos un poquito de contenido al App
componente, por lo que tenemos algunos elementos para diseñar, junto con un botón y una función que realmente nos permiten alternar el tema.
El terminado
App.tsx
Archivo
La salsa secreta está en la línea 14 donde agregamos data-theme={theme}
a nuestro nivel superior <div>
. Ahora, cambiando el valor de theme
, elegimos si anulamos o no las variables CSS en :root
con los que están en el data-theme="dark"
sección de la index.css
Archivo.
Lo último que tenemos que hacer es agregar un estilo que use esas variables CSS que hicimos anteriormente, ¡y estará listo y funcionando! Abierto App.css
y suelte este CSS allí:
.App {
color: var(--text-primary);
background-color: var(--background);
font-size: large;
font-weight: bold;
padding: 20px;
height: calc(100vh - 40px);
transition: all .5s;
}
button {
color: var(--text-primary);
background-color: var(--background);
border: 2px var(--text-primary) solid;
float: right;
transition: all .5s;
}
Ahora el fondo y el texto de la principal <div>
y el fondo, el texto y el esquema del <button>
confiar en las variables CSS. Eso significa que cuando cambia el tema, todo lo que depende de esas variables también se actualiza. También tenga en cuenta que agregamos transition: all .5s
tanto para el App
y <button>
para una transición suave entre temas de color.
Ahora, regrese al navegador que ejecuta la aplicación, y esto es lo que obtenemos:
¡Tada! Agreguemos otro componente solo para mostrar cómo funciona el sistema si estamos construyendo una aplicación real. Agregaremos un /components
carpeta en /src
, Poner un /square
carpeta en /components
y agregue un Square.tsx
y square.css
, al igual que:
Vamos a importarlo de nuevo a App.tsx
, al igual que:
Esto es lo que tenemos ahora como resultado:
¡Y ahí vamos! Obviamente, este es un caso bastante básico en el que solo estamos usando un tema predeterminado (claro) y un tema secundario (oscuro). Pero si su aplicación lo requiere, este sistema podría usarse para implementar múltiples opciones de temas. Personalmente, estoy pensando en darle a mi próximo proyecto opciones para claro, oscuro, chocolate y fresa. ¡Vuélvete loco!
Bono: Integración con React Scoped CSS:
El uso de React Scoped CSS es mi forma favorita de mantener encapsulado el CSS de cada componente para evitar el desorden de la colisión de nombres y la herencia de estilo no intencionada. Mi anterior opción para esto eran los módulos CSS, pero eso tiene la desventaja de hacer que el DOM en el navegador parezca que un robot escribió todos los nombres de las clases … porque ese es exactamente el caso. Esta falta de legibilidad humana hace que la depuración sea mucho más molesta de lo que debería ser. Ingrese React Scoped CSS. Podemos seguir escribiendo CSS (o Sass) exactamente como lo hemos hecho, y el resultado parece que lo escribió un humano.
Dado que el repositorio de CSS de React Scoped proporciona instrucciones de instalación completas y detalladas, simplemente las resumiré aquí.
Primero, instale y configure Create React App Configuration Override (CRACO) de acuerdo con sus instrucciones. Craco es una herramienta que nos permite anular parte de la configuración predeterminada del paquete web que se incluye en create-react-app (CRA). Normalmente, si desea ajustar el paquete web en un proyecto de CRA, primero debe “expulsar” el proyecto, que es un operación irreversible, y lo hace completamente responsable de todas las dependencias que normalmente se manejan por usted. Por lo general, desea evitar la expulsión a menos que realmente sepa lo que está haciendo y tenga una buena razón para seguir ese camino. En cambio, CRACO nos permite hacer algunos ajustes menores a la configuración de nuestro paquete web sin que las cosas se pongan complicadas.
Una vez hecho esto, instale el paquete React Scoped CSS:
npm i craco-plugin-scoped-css
(Las instrucciones README utilizan yarn
para la instalación en lugar de npm
, pero cualquiera está bien). Ahora que está instalado, simplemente cambie el nombre de los archivos CSS agregando .scoped
antes de .css
, al igual que:
app.css -> app.scoped.css
Y debemos asegurarnos de que estamos usando un nuevo nombre al importar ese CSS a un componente:
import './app.css'; -> import './app.scoped.css';
Ahora todo el CSS está encapsulado para que solo se aplique a los componentes a los que se importan. Funciona usando data-*
propiedades, al igual que nuestro sistema de temas, por lo que cuando se importa un archivo CSS con ámbito en un componente, todos los elementos de ese componente se etiquetan con una propiedad, como data-v-46ef2374
, y los estilos de ese archivo se ajustan para que solo se apliquen a los elementos con esa propiedad de datos exacta.
Todo eso es maravilloso, pero el pequeño truco para que funcione con este sistema de temas es que explícitamente no quiero las variables CSS encapsuladas; queremos que se apliquen a todo el proyecto. Entonces, simplemente no cambiamos index.css
tener un alcance en él … en otras palabras, podemos dejar ese archivo CSS solo. ¡Eso es! Ahora tenemos un poderoso sistema de tematización que funciona en armonía con CSS con alcance: ¡estamos viviendo el sueño!
Demostración en vivo de GitHub Repo
Muchas gracias por leer esta guía, y si te ayudó a construir algo increíble, ¡me encantaría saberlo!