Introducción a los diferentes tipos de almacenamiento del navegador
En el desarrollo back-end, el almacenamiento es una parte común del trabajo. Los datos de las aplicaciones se almacenan en bases de datos, los archivos en el almacenamiento de objetos, los datos transitorios en cachés… existen posibilidades aparentemente infinitas para almacenar cualquier tipo de datos. Pero el almacenamiento de datos no se limita sólo al back-end. La interfaz (el navegador) también está equipada con muchas opciones para almacenar datos. Podemos aumentar el rendimiento de nuestra aplicación, guardar las preferencias del usuario, mantener el estado de la aplicación en múltiples sesiones o incluso en diferentes computadoras, utilizando este almacenamiento.
En este artículo veremos las diferentes posibilidades para almacenar datos en el navegador. Cubriremos tres casos de uso de cada método para comprender los pros y los contras. Al final, podrá decidir qué almacenamiento es el mejor para su caso de uso. ¡Así que comencemos!
La localStorage
API
localStorage
es una de las opciones de almacenamiento más populares en el navegador y la opción preferida de muchos desarrolladores. Los datos se almacenan entre sesiones, nunca se comparten con el servidor y están disponibles para todas las páginas bajo el mismo protocolo y dominio. El almacenamiento está limitado a ~5 MB.
Sorprendentemente, el equipo de Google Chrome no recomienda el uso de esta opción, ya que bloquea el hilo principal y no es accesible para los trabajadores web ni para los trabajadores de servicios. Lanzaron un experimento, KV Storage , como una versión mejor, pero fue solo una prueba que no parece haber llegado a ninguna parte todavía.
La localStorage
API está disponible window.localStorage
y solo puede guardar cadenas UTF-16. Debemos asegurarnos de convertir los datos a cadenas antes de guardarlos en localStorage
. Las tres funciones principales son:
setItem('key',
'value')
getItem('key')
removeItem('key')
Todos son sincrónicos, lo que hace que sea sencillo trabajar con ellos, pero bloquean el hilo principal.
Cabe mencionar que localStorage
tiene un gemelo llamado sessionStorage
. La única diferencia es que los datos almacenados sessionStorage
durarán solo para la sesión actual, pero la API es la misma.
Veámoslo en acción. El primer ejemplo demuestra cómo utilizarlo localStorage
para almacenar las preferencias del usuario. En nuestro caso, es una propiedad booleana que activa o desactiva el tema oscuro de nuestro sitio.
Puede marcar la casilla de verificación y actualizar la página para ver que el estado se guarda en todas las sesiones. Eche un vistazo a las funciones save
y load
para ver cómo convierto el valor en cadena y cómo lo analizo. Es importante recordar que solo podemos almacenar cadenas.
Este segundo ejemplo carga nombres de Pokémon de la PokéAPI .
Enviamos una solicitud GET usando fetch
y enumeramos todos los nombres en un ul
elemento. Al recibir la respuesta, la almacenamos en caché localStorage
para que nuestra próxima visita pueda ser mucho más rápida o incluso funcionar sin conexión. Tenemos que usar JSON.stringify
para convertir los datos a cadena y JSON.parse
leerlos del caché.
En este último ejemplo, demuestro un caso de uso en el que el usuario puede navegar a través de diferentes páginas de Pokémon y la página actual se guarda para las próximas visitas.
El problema localStorage
, en este caso, es que el estado se guarda localmente. Este comportamiento no nos permite compartir la página deseada con nuestros amigos. Más adelante veremos cómo superar este problema.
También usaremos estos tres ejemplos en las siguientes opciones de almacenamiento. Bifurqué los Pens y simplemente cambié las funciones relevantes. El esqueleto general es el mismo para todos los métodos.
La API IndexedDB
IndexedDB es una solución de almacenamiento moderna en el navegador. Puede almacenar una cantidad significativa de datos estructurados, incluso archivos y blobs. Como toda base de datos, IndexedDB indexa los datos para ejecutar consultas de manera eficiente. Es más complejo utilizar IndexedDB. Tenemos que crear una base de datos, tablas y usar transacciones.
En comparación con localStorage
, IndexedDB requiere mucho más código. En los ejemplos, uso la API nativa con un contenedor Promise , pero recomiendo encarecidamente usar bibliotecas de terceros para ayudarte. Mi recomendación es localForage porque usa la misma localStorage
API pero la implementa de manera progresiva, lo que significa que si su navegador admite IndexedDB, la usará; y si no, volverá a caer localStorage
.
¡Codifiquemos y vayamos a nuestro ejemplo de preferencias de usuario!
idb
es el contenedor de Promise que usamos en lugar de trabajar con una API basada en eventos de bajo nivel. Son casi idénticos, así que no te preocupes. Lo primero que hay que notar es que cada acceso a la base de datos es asíncrono, lo que significa que no bloqueamos el hilo principal. En comparación con localStorage
, esta es una gran ventaja.
Necesitamos abrir una conexión a nuestra base de datos para que esté disponible en toda la aplicación para lectura y escritura. Le damos a nuestra base de datos un nombre, my-db
una versión de esquema, 1 y una función de actualización para aplicar cambios entre versiones. Esto es muy similar a las migraciones de bases de datos. Nuestro esquema de base de datos es simple: solo un almacén de objetos, preferences
. Un almacén de objetos es el equivalente a una tabla SQL. Para escribir o leer de la base de datos, debemos utilizar transacciones. Esta es la parte tediosa del uso de IndexedDB. Eche un vistazo a las novedades save
y load
funciones en la demostración.
No hay duda de que IndexedDB tiene muchos más gastos generales y la curva de aprendizaje es más pronunciada en comparación con localStorage
. Para los casos de valores clave, podría tener más sentido utilizar localStorage
una biblioteca de terceros que nos ayude a ser más productivos.
Los datos de aplicaciones, como en nuestro ejemplo de Pokémon, son el punto fuerte de IndexedDB. Puedes almacenar cientos de megabytes e incluso más en esta base de datos. ¡Puedes almacenar todos los Pokémon en IndexedDB y tenerlos disponibles sin conexión e incluso indexados! Este es definitivamente el que debe elegir para almacenar datos de aplicaciones.
Me salté la implementación del tercer ejemplo, ya que IndexedDB no introduce ninguna diferencia en este caso en comparación con localStorage
. Incluso con IndexedDB, el usuario aún no compartirá la página seleccionada con otros ni la marcará como favorita para uso futuro. Ninguno de los dos es adecuado para este caso de uso.
Galletas
El uso de cookies es una opción de almacenamiento única. Es el único almacenamiento que también se comparte con el servidor. Las cookies se envían como parte de cada solicitud. Puede ser cuando el usuario navega por las páginas de nuestra aplicación o cuando el usuario envía solicitudes Ajax. Esto nos permite crear un estado compartido entre el cliente y el servidor, y también compartir el estado entre múltiples aplicaciones en diferentes subdominios. Esto no es posible con otras opciones de almacenamiento que se describen en este artículo. Una advertencia: las cookies se envían con cada solicitud, lo que significa que debemos mantener nuestras cookies pequeñas para mantener un tamaño de solicitud decente.
El uso más común de las cookies es la autenticación, que está fuera del alcance de este artículo. Al igual que las localStorage
cookies, las cookies solo pueden almacenar cadenas. Las cookies se concatenan en una cadena separada por punto y coma y se envían en el encabezado de la cookie de la solicitud. Puede establecer muchos atributos para cada cookie, como caducidad, dominios permitidos, páginas permitidas y muchos más.
En los ejemplos, muestro cómo manipular las cookies a través del lado del cliente, pero también es posible cambiarlas en la aplicación del lado del servidor.
Guardar las preferencias del usuario en una cookie puede ser una buena opción si el servidor puede utilizarla de alguna manera. Por ejemplo, en el caso de uso del tema, el servidor puede entregar el archivo CSS relevante y reducir el tamaño potencial del paquete (en caso de que estemos renderizando del lado del servidor). Otro caso de uso podría ser compartir estas preferencias entre múltiples aplicaciones de subdominio sin una base de datos.
Leer y escribir cookies con JavaScript no es tan sencillo como podría pensar. Para guardar una nueva cookie, debe configurarla document.cookie
; consulte la save
función en el ejemplo anterior. Configuro la dark_theme
cookie y le agrego un max-age
atributo para asegurarme de que no caduque cuando se cierre la pestaña. Además, agrego los atributos SameSite
y Secure
. Estos son necesarios porque CodePen usa iframe para ejecutar los ejemplos, pero no los necesitará en la mayoría de los casos. Leer una cookie requiere analizar la cadena de la cookie.
Una cadena de cookies tiene este aspecto:
key1=value1;key2=value2;key3=value3
Entonces, primero tenemos que dividir la cadena por punto y coma. Ahora tenemos una matriz de cookies en forma de key1=value1
, por lo que necesitamos encontrar el elemento correcto en la matriz. Al final, dividimos por el signo igual y obtenemos el último elemento de la nueva matriz. Un poco tedioso, pero una vez que implementas la getCookie
función (o la copias de mi ejemplo :P) puedes olvidarla.
¡Guardar datos de la aplicación en una cookie puede ser una mala idea! Aumentará drásticamente el tamaño de la solicitud y reducirá el rendimiento de la aplicación. Además, el servidor no puede beneficiarse de esta información ya que es una versión obsoleta de la información que ya tiene en su base de datos. Si utiliza cookies, asegúrese de que sean pequeñas.
El ejemplo de paginación tampoco es adecuado para las cookies, al igual que localStorage
IndexedDB. La página actual es un estado temporal que nos gustaría compartir con otros y ninguno de estos métodos lo logra.
almacenamiento de URL
La URL no es un almacenamiento en sí, pero es una excelente manera de crear un estado que se pueda compartir. En la práctica, significa agregar parámetros de consulta a la URL actual que pueden usarse para recrear el estado actual. El mejor ejemplo serían las consultas de búsqueda y los filtros. Si buscamos el término flexbox
en CSS-Tricks, la URL se actualizará a https://css-tricks.com/?s=flexbox
. ¿Ves lo fácil que es compartir una consulta de búsqueda una vez que usamos la URL? Otra ventaja es que puede simplemente presionar el botón Actualizar para obtener resultados más recientes de su consulta o incluso marcarla como favorita.
Solo podemos guardar cadenas en la URL y su longitud máxima es limitada, por lo que no tenemos tanto espacio. Tendremos que mantener nuestro estado pequeño. A nadie le gustan las URL largas e intimidantes.
Nuevamente, CodePen usa iframe para ejecutar los ejemplos, por lo que no puede ver que la URL realmente cambie. No te preocupes, porque todos los detalles están ahí para que puedas usarlos donde quieras.
Podemos acceder a la cadena de consulta window.location.search
y, por suerte, se puede analizar usando la URLSearchParams
clase. Ya no es necesario aplicar ningún análisis de cadenas complejo. Cuando queramos leer el valor actual, podemos usar la get
función. Cuando queramos escribir, podemos usar set
. No basta con establecer sólo el valor; También necesitamos actualizar la URL. Esto se puede hacer usando history.pushState
o history.replaceState
, dependiendo del comportamiento que queramos lograr.
No recomendaría guardar las preferencias de un usuario en la URL, ya que tendremos que agregar este estado a cada URL que visite el usuario y no podemos garantizarlo; por ejemplo, si el usuario hace clic en un enlace de la Búsqueda de Google.
Al igual que las cookies, no podemos guardar datos de la aplicación en la URL porque tenemos un espacio mínimo. E incluso si logramos almacenarlo, la URL será larga y no invitará a hacer clic. Podría parecer una especie de ataque de phishing.
Al igual que en nuestro ejemplo de paginación, el estado de la aplicación temporal es el que mejor se adapta a la cadena de consulta de URL. Nuevamente, no puede ver los cambios de URL, pero la URL se actualiza con el ?page=x
parámetro de consulta cada vez que hace clic en una página. Cuando se carga la página web, busca este parámetro de consulta y busca la página correcta en consecuencia. Ahora podremos compartir esta URL con nuestros amigos para que puedan disfrutar de nuestros Pokémon favoritos.
Cache API
La API de caché es un almacenamiento para el nivel de red. Se utiliza para almacenar en caché las solicitudes de red y sus respuestas. La API de caché encaja perfectamente con los trabajadores del servicio. Un trabajador de servicio puede interceptar cada solicitud de red y, utilizando la API de caché, puede almacenar en caché fácilmente ambas solicitudes. El trabajador del servicio también puede devolver un elemento de caché existente como respuesta de la red en lugar de recuperarlo del servidor. Al hacerlo, puede reducir los tiempos de carga de la red y hacer que su aplicación funcione incluso sin conexión. Originalmente, fue creada para trabajadores de servicios, pero en los navegadores modernos, la API de caché también está disponible en contextos de ventana, iframe y trabajador. Es una API muy poderosa que puede mejorar drásticamente la experiencia del usuario de la aplicación.
Al igual que IndexedDB, el almacenamiento de la API de caché no está limitado y puede almacenar cientos de megabytes e incluso más si es necesario. La API es asincrónica por lo que no bloqueará su hilo principal. Y es accesible a través de la propiedad global caches
.
Para leer más sobre la API de caché, el equipo de Google Chrome ha creado un excelente tutorial.
Chris creó un Pen increíble con un ejemplo práctico de combinación de trabajadores de servicio y la API de caché.
Abrir demostración
Bonificación: extensión del navegador
Si crea una extensión de navegador, tiene otra opción para almacenar sus datos. Lo descubrí mientras trabajaba en mi extensión, daily.dev. Está disponible a través de chrome.storage
o browser.storage
, si usas polyfill de Mozilla. Asegúrese de solicitar un permiso de almacenamiento en su manifiesto para obtener acceso.
Hay dos tipos de opciones de almacenamiento, local y sincronizado. El almacenamiento local se explica por sí mismo; significa que no se comparte ni se guarda localmente. El almacenamiento de sincronización se sincroniza como parte de la cuenta de Google y en cualquier lugar donde instale la extensión con la misma cuenta se sincronizará este almacenamiento. Característica bastante interesante si me preguntas. Ambos tienen la misma API, por lo que es muy fácil alternar, si es necesario. Es almacenamiento asíncrono, por lo que no bloquea el hilo principal como localStorage
. Desafortunadamente, no puedo crear una demostración para esta opción de almacenamiento porque requiere una extensión del navegador, pero es bastante simple de usar y casi como localStorage
. Para obtener más información sobre la implementación exacta, consulte los documentos de Chrome.
Conclusión
El navegador tiene muchas opciones que podemos utilizar para almacenar nuestros datos. Siguiendo el consejo del equipo de Chrome, nuestro almacenamiento preferido debería ser IndexedDB. Es un almacenamiento asíncrono con suficiente espacio para almacenar lo que queramos. localStorage
No se recomienda, pero es más fácil de usar que IndexedDB. Las cookies son una excelente manera de compartir el estado del cliente con el servidor, pero se utilizan principalmente para la autenticación.
Si desea crear páginas con un estado que se pueda compartir, como una página de búsqueda, utilice la cadena de consulta de la URL para almacenar esta información. Por último, si crea una extensión, asegúrese de leer sobre chrome.storage
.
Deja un comentario