javascript - ¿Cuál es la forma adecuada de alternar entre los modos oscuro y claro en SolidJS + SUID?
Soy algo nuevo en SolidJS, y ahora estoy tratando de entender cómo el ThemeProvider de SUID difiere del de SolidJS (por ejemplo, este ejemplo de proveedor/contexto en la documentación). Un ejemplo simple sería cómo cambiar entre los modos claro y oscuro desde un componente hijo. ¿Cuál es la manera correcta?
He intentado algo así hasta ahora, pero no ha funcionado, pensé que la reactividad de la señal sería suficiente, pero creo que estoy malinterpretando algún concepto fundamental en algún lugar.
Si el ejemplo siguiente tiene éxito, entonces el fondo debería alternar entre negro y blanco.
import { createSignal } from "solid-js";
import { createTheme } from "@suid/material";
import { green, orange } from "@suid/material/colors";
const lightTheme = createTheme({
palette: {
mode: "light",
background: {
default: "#000",
},
}
});
const darkTheme = createTheme({
palette: {
mode: "dark",
background: {
default: "#fff",
},
}
});
const [theme, setTheme] = createSignal(darkTheme);
function toggleTheme() {
theme() === lightTheme
? setTheme(darkTheme)
: setTheme(lightTheme);
}
function ChildComponent() {
return (
<FormControlLabel
onChange={(e) => {
toggleTheme();
}}
label=""
control={<Switch />}
/>
)
}
function App() {
return (
<>
<CssBaseline />
<ThemeProvider theme={theme()}>
<ChildComponent />
</ThemeProvider>
</>
);
}
Respuesta #1
Resumen;
La forma en que SolidJS funciona es bastante diferente de React. Rastrea las actualizaciones o la reactividad esencialmente en función de llamadas a funciones, y tenemos que hacer uso de primitivas reactivas en SolidJS para poder asegurarnos de que la interfaz de usuario sea reactiva. Por lo tanto, necesitarás crear tu tema utilizando una primitiva reactiva.
Estableciendo la línea base
La guía de SolidJS sobre reactividad describe todos los tipos de primitivas reactivas y cómo funcionan.
La reactividad en SolidJS solo funciona si se accede a cualquier primitiva reactiva en un ámbito reactivo, por así decirlo, que es, cualquier otra primitiva reactiva (usando una señal dentro de un efecto, por ejemplo) o dentro del código JSX. Si este es el caso, ocurre una re-renderización y las actualizaciones se reflejan. Para citar la documentación de SolidJS -
- Toda la reactividad se rastrea a partir de las llamadas de función, ya sea directamente o ocultas bajo getters/proxies, y se desencadena al acceder a propiedades. Esto significa que dónde accedes a las propiedades en objetos reactivos es importante.
- Los componentes y las devoluciones de llamada de los flujos de control no rastrean los ámbitos y solo se ejecutan una vez. Esto significa que desestructurar o hacer lógica a nivel superior en tus componentes no se volverá a ejecutar. Debes acceder a estas Señales, Almacenes y props desde dentro de otros primitivos reactivos o del JSX para que esa parte del código se vuelva a evaluar.
Los Signals & Memos devuelven un tipo Accessor<T> que, al ser llamado, en un contexto reactivo provoca una actualización. Podrías pensar -
Pero estoy usando la señal dentro del JSX (en mi ejemplo), y ese es un contexto reactivo, entonces ¿qué está mal? :
Sí, lo estás usando así, y no es tu culpa que no funcione, es culpa de SUID. Recuerda la regla sobre destructuración de la documentación -
Esto significa que desestructurar o realizar lógica a nivel superior en tus componentes no se volverá a ejecutar.
Esta es la razón por la que no funciona. Verás, la biblioteca SUID es simplemente MUI usando SolidJS en lugar de React. La arquitectura es la misma que la de MUI. Para inyectar estilos en todos los componentes, la biblioteca MUI hace uso de la utilidad styled; si el usuario no utiliza un ThemeProvider, se inyecta un tema predeterminado, de lo contrario, si hay un ThemeProvider, se usa useTheme para obtener ese tema y crear los componentes. Lo mismo ocurre con SUID, que también hace uso de useTheme internamente para crear los componentes, lo cual no es más que una desestructuración como la siguiente -
const theme = useTheme();
Entonces, el tema se está actualizando, pero no hay actualizaciones. Pasemos a la depuración paso a paso.
Depuración: Entender Qué Está Pasando
En tu ejemplo, estás tratando de hacerlo como se hace en React. No te culpo. Usemos tu ejemplo para compararlo con React y ver qué está pasando realmente.
Aquí hay un ejemplo de React MUI, y aquí hay un ejemplo de SolidJS con SUID. Cuando hacemos clic en el botón frente a "From Signal" el manejador se dispara en ambos ejemplos. Sin embargo, el comportamiento no es el mismo. ¿Qué falta?
Vemos que en MUI, el estado del tema se refleja correctamente en los botones y el modo de tema también se refleja correctamente, sin embargo, en SolidJS el valor del modo de tema se refleja correctamente desde el botón usando el valor de la señal, pero el botón que usa el tema de useTheme dentro del contexto, no parece poder obtenerlo correctamente. Pero en realidad sí está obteniendo el tema correcto, SolidJS pasa los objetos tal como son a través del hook useContext. Esto sucede debido a la desestructuración, la interfaz de usuario no se vuelve a renderizar. Déjame demostrártelo.
Cambiemos alguna parte del código en nuestro ejemplo de SolidJS, para que se vea así, pasaremos la señal theme envuelta dentro de un arreglo en lugar de pasarla directamente en el proveedor; con eso necesitaremos hacer uso de componentes HTML normales, porque con un tema de tipo arreglo, los componentes de la librería darán error al no poder acceder al tema.
¿Qué? ¿Ahora ambos botones reflejan el modo de tema correcto en SolidJS también? :
Sí, porque esta vez, estamos desestructurando un array, y tiene la referencia a la señal, así que solo creamos un envoltorio para obtener ese valor, ¡y funciona!
Ya basta de todo esto, ¿cómo arreglo entonces el tema? :
Pasemos a eso.
¿Cómo implementar la tematización?
Como se mencionó al inicio, necesitaremos hacer uso de primitivas reactivas, y como ya se ha visto, las señales realmente no ayudan, así que utilizaremos la primitiva reactiva createMemo y este es el enfoque que tomaremos -
import {
createTheme,
ThemeProvider,
Typography,
useTheme,
} from '@suid/material';
import Button from '@suid/material/Button';
import { createPalette } from '@suid/material/styles/createPalette';
import { createSignal, createMemo, createEffect } from 'solid-js';
function Btn(props: any) {
const theme = useTheme();
return (
<div
style={{
display: 'flex',
gap: '1rem',
alignItems: 'center',
margin: '10px',
}}
>
<Typography>From Context: </Typography>
<Button variant="contained" color="success">
{theme.palette.mode}
</Button>
</div>
);
}
export default function Counter(props: any) {
const [themeMode, setThemeMode] = createSignal<'light' | 'dark'>('light');
const palette = createMemo(() => {
return createPalette({ mode: themeMode() });
});
const theme = createTheme({ palette: palette });
function onClick() {
setThemeMode((prev) => (prev === 'light' ? 'dark' : 'light'));
}
return (
<ThemeProvider theme={theme}>
<div
style={{
display: 'flex',
gap: '1rem',
alignItems: 'center',
margin: '10px',
}}
>
<Typography>From Signal: </Typography>
<Button variant="contained" color="success" onClick={onClick}>
{themeMode()}
</Button>
</div>
<Btn />
<div
style={{
display: 'flex',
gap: '1rem',
alignItems: 'center',
justifyContent: 'center',
margin: '15px 50px',
fontWeight: 'bold',
}}
>
SolidJS
</div>
</ThemeProvider>
);
}
¡Ahora funciona! ¡Finalmente tenemos nuestro tema!
Espera, ¿por qué tenemos que hacerlo así? :
El primitivo reactivo createMemo es una señal y un efecto al mismo tiempo. Produce un valor reactivo, y en todas las funciones donde se utiliza ese valor reactivo, la función se registra en la pila interna del runtime de SolidJS como suscriptor.
Entonces, ¿no puedo simplemente usar
createThemedentro decreateMemo? :
No, no puedes. La razón es que, si usas el createMemo así, obtendrás un objeto theme que está memorizado y dado que el hook useContext pasa los datos tal cual, el objeto theme será desestructurado como antes por useTheme y perderemos la reactividad.
La razón por la que este enfoque funciona es que, cuando la señal themeMode cambia, se crea nuevamente la palette, y con eso, se actualiza la variable theme. Después de que todo eso se ha hecho y los nodos en el árbol están presentes, se ejecuta un efecto para actualizar el árbol, y por lo tanto vemos que el tema funciona.
Respuesta #2
Solid no tiene un proveedor de temas, pero proporciona una API de contexto para crear un proveedor de temas, en este caso ThemeProvider.
ThemeProvider es un componente que pasa datos de tema a sus hijos. No solo a sus hijos directos, sino a cualquier hijo que esté debajo de él.
Para el ejemplo, los datos son el valor devuelto por la señal theme().
FormControlLabel bajo ChildComponent proporciona una forma de alternar entre los temas oscuro y claro cambiando el tema activo a través de setTheme.
Cualquier componente que tenga acceso a setTheme puede establecer un nuevo tema.
Se puede crear un nuevo tema mediante createTheme. Proporciona los requisitos mínimos para un valor de tema. Un tema es un objeto con un valor llamado palette que tiene mode, etc.
Lo que estás intentando hacer es usar un valor de contexto para pasar datos, un objeto de JavaScript con valores CSS, a los componentes descendentes. Dado que el código no tiene problemas pero no sabes cómo funciona, es mejor que aprendas cómo funciona Solid y cómo la API de contexto encaja en su sistema reactivo.
Puedes consultar esta respuesta sobre cómo usar Context API: https://stackoverflow.com/a/74492899/7134134


