Armazenar dados de aplicativos offline tornou-se uma necessidade no desenvolvimento da web moderno. O navegador embutido localStorage pode servir como um armazenamento de dados para aplicações simples e dados leves, mas são insuficientes quando se trata de estruturar esses dados ou armazenar uma quantidade considerável deles.

Além disso, só podemos armazenar dados de string em localStorage , que são suscetíveis a Ataques XSS , e não fornece muitas funcionalidades para consultar dados.

É aqui que o IndexedDB se destaca. Com IndexedDB, podemos criar bancos de dados estruturados no navegador, armazenar quase tudo nesses bancos de dados e realizar vários tipos de consultas para nossos dados.

Neste artigo, veremos do que se trata IndexedDB e como podemos usar Dexie.js, um wrapper minimalista para IndexedDB, para lidar com o armazenamento de dados offline em nossos aplicativos da web.

Como funciona o IndexedDB

IndexedDB é um banco de dados não relacional embutido para navegadores. Ele dá aos desenvolvedores a capacidade de armazenar dados persistentemente no navegador, permitindo o uso contínuo de aplicativos da web, mesmo quando offline. Dois termos frequentes que você verá ao trabalhar com IndexedDB são banco de dados e armazenamento de objeto. Vamos explorar aqueles abaixo.

Criando um banco de dados com IndexedDB

Os bancos de dados IndexedDB são exclusivos para cada aplicativo da web. Isso significa que um aplicativo só pode acessar dados de um banco de dados IndexedDB executado no mesmo domínio ou subdomínio que ele. O banco de dados é o que abriga os armazenamentos de objetos, que por sua vez contêm os dados armazenados. Para trabalhar com bancos de dados IndexedDB, precisaremos abri-los (ou nos conectar a):

 const initializeDb=indexedDB.open ('name_of_database', version) 

O argumento name_of_database no método indexedDb.open () servirá como o nome do banco de dados que está sendo criado, e o argumento version é um número que representa a versão do banco de dados.

Em IndexedDB, usamos armazenamentos de objetos para estruturar o banco de dados e sempre que quisermos atualizar nossa estrutura de banco de dados, precisaremos atualizar a versão para um valor mais alto. Isso significa que se começarmos com a versão 1, da próxima vez que quisermos atualizar a estrutura de nosso banco de dados, precisaremos alterar a versão no método indexedDb.open () para 2 ou superior.

Criando armazenamentos de objetos com IndexedDB

Armazenamentos de objetos são semelhantes a tabelas em bancos de dados relacionais como PostgreSQL e coleções em bancos de dados de documentos como MongoDB. Para criar armazenamentos de objetos em IndexedDB, precisaremos chamar um método onupgradeneeded () da variável initializeDb declarada anteriormente:

 initializeDb.onupgradeneeded=()=> { const database=initializeDb.result database.createObjectStore ('name_of_object_store', {autoIncrement: true})
} 

No bloco acima, obtemos nosso banco de dados da propriedade initializeDb.result e então usamos seu método createObjectstore () para criar nosso armazenamento de objeto. O segundo argumento, {autoIncrement: true} , diz ao IndexedDB para fornecer/incrementar automaticamente o ID de itens no armazenamento de objeto.

Eu deixei de fora outros termos como transação e cursor porque trabalhar com a API IndexedDB de baixo nível dá muito trabalho. É por isso que precisamos de Dexie.js, um wrapper minimalista para IndexedDB. Vamos ver como Dexie simplifica todo o processo de criação de um banco de dados, armazenamento de objetos, armazenamento de dados e consulta de dados de nosso banco de dados.

Usando Dexie para armazenar dados offline

Com Dexie, criar bancos de dados IndexedDB e armazenamentos de objetos é muito mais fácil:

 const db=new Dexie ('exampleDatabase')
db.version (1).stores ({ name_of_object_store:'++ id, nome, preço', name_of_another_object_store:'++ id, title'
}) 

No bloco acima, criamos um novo banco de dados denominado exampleDatabase e o atribuímos como um valor à variável db . Usamos o método db.version (version_number).stores () para criar armazenamentos de objetos para nosso banco de dados.

O valor de cada armazenamento de objeto representa sua estrutura. Por exemplo, ao armazenar dados no primeiro armazenamento de objeto, precisaremos fornecer a um objeto as propriedades nome e preço . A opção ++ id funciona exatamente como o argumento {autoIncrement: true} que usamos antes ao criar armazenamentos de objetos.

Observe que precisaremos instalar e importar o pacote dexie antes de usá-lo em nosso aplicativo. Veremos como fazer isso quando começarmos a construir nosso projeto de demonstração.

O que iremos construir

Para nosso projeto de demonstração, construiremos um aplicativo de lista de mercado com Dexie.js e React. Nossos usuários poderão adicionar itens que pretendem comprar em sua lista de mercado, excluir os itens ou marcá-los como comprados.

Veremos como usar o gancho Dexie useLiveQuery para observar as mudanças em nosso banco de dados IndexedDB e renderizar novamente nosso componente React quando o banco de dados for atualizado. Esta será a aparência de nosso aplicativo:

Aplicativo de lista de mercado finalizado

Configurando nosso aplicativo

Para começar, usaremos um modelo GitHub que criei para a estrutura e o design de nosso aplicativo. Aqui está um link para o modelo . Clicar no botão Usar este modelo criará um novo repositório para você com o modelo existente, que você pode clonar e usar.

Como alternativa, com GitHub CLI instalado em sua máquina, você pode executar o seguinte comando para criar um repo chamado market-list-app do modelo GitHub da lista de mercado:

 gh repo criar market-list-app--template ebenezerdon/market-list-template 

Feito isso, você pode clonar e abrir seu novo aplicativo em um editor de código. Usar seu terminal para executar o seguinte comando no diretório do aplicativo deve instalar as dependências npm e iniciar seu novo aplicativo:

 npm install && npm start 

Você deve conseguir ver seu novo aplicativo React ao navegar para o URL local na mensagem de sucesso, geralmente http://localhost: 3000 . Seu novo aplicativo deve ser assim:

Visualização do aplicativo Market List

Ao abrir o arquivo ./src/App.js , você notará que nosso componente App contém apenas o código JSX para o aplicativo de lista de mercado. Estamos usando classes da Estrutura de materialização para o estilo e nós’incluímos seu link CDN no arquivo ./public/index.html . A seguir, veremos como podemos usar o Dexie para criar e gerenciar nossos dados.

Criando nosso banco de dados com Dexie

Para usar Dexie.js para armazenamento offline em nosso aplicativo React, começaremos executando o seguinte comando em nosso terminal para instalar o dexie e os dexie-react-hooks pacotes:

 npm i-s dexie dexie-react-hooks 

Usaremos o gancho useLiveQuery do pacote dexie-react-hooks para observar as alterações e renderizar novamente nosso componente React quando uma atualização for feita no Banco de dados IndexedDB.

Vamos adicionar as seguintes instruções de importação ao nosso arquivo ./src/App.js . Isso importará Dexie e o gancho useLiveQuery :

 importar Dexie de'dexie'
import {useLiveQuery} de"dexie-react-hooks"; 

A seguir, criaremos um novo banco de dados denominado MarketList e, em seguida, declararemos nosso armazenamento de objeto, items :

 const db=new Dexie ('MarketList');
db.version (1).stores ( {itens:"++ id, nome, preço, itemHasBeenPurchased"}
) 

Nosso armazenamento de objeto items estará esperando um objeto com as propriedades name , price e itemHasBeenPurchased , enquanto o id será fornecido por Dexie. Ao adicionar novos dados ao nosso armazenamento de objeto, usaremos um valor booleano padrão de false para a propriedade itemHasBeenPurchased e, em seguida, atualizaremos para true sempre que comprarmos um item de nossa lista de mercado.

O gancho Dexie React

Vamos criar uma variável para armazenar todos os nossos itens. Usaremos o gancho useLiveQuery para obter dados do armazenamento de objeto items e observaremos as alterações nele para que, quando houver uma atualização dos items armazenamento de objeto, nossa variável allItems será atualizada e nosso componente renderizado novamente com os novos dados. Faremos isso dentro do componente App :

 const App=()=> { const allItems=useLiveQuery (()=> db.items.toArray (), []); if (! allItems) retorna nulo ...
} 

No bloco acima, criamos uma variável chamada allItems e chamamos o gancho useLiveQuery como seu valor. A sintaxe para nosso gancho useLiveQuery é semelhante a useEffect hook . Ele espera uma função e uma matriz de suas dependências como argumentos. Nosso argumento de função retorna a consulta do banco de dados.

Aqui, estamos obtendo todos os dados no armazenamento de objetos items em um formato de matriz. Na próxima linha, usamos uma condição para informar ao nosso componente que se a variável allItems for indefinida, isso significa que as consultas ainda estão carregando.

Adicionando itens ao nosso banco de dados

Ainda no componente App , vamos criar uma função chamada addItemToDb , que usaremos para adicionar itens ao nosso banco de dados. Chamaremos essa função sempre que clicarmos no botão ADICIONAR ITEM . Lembre-se de que nosso componente será renderizado novamente sempre que o banco de dados for atualizado.

...
const addItemToDb=evento async=> { event.preventDefault () const name=document.querySelector ('. item-name'). value const price=document.querySelector ('. item-price'). value aguarde db.items.add ({ nome, preço: Número (preço), itemHasBeenPurchased: false }) }
... 

Em nossa função addItemToDb , obtemos o nome do item e os valores do preço dos campos de entrada do formulário e, em seguida, usamos o método db. [name_of_object_store].add para adicionar nossos novos dados de item para o armazenamento de objeto items . Também estamos usando um valor padrão de false para a propriedade itemHasBeenPurchased .

Removendo itens de nosso banco de dados

Agora que temos a função addItemToDb , vamos criar uma função chamada removeItemFromDb para remover dados de nosso armazenamento de objetos items :

...
const removeItemFromDb=async id=> { aguardar db.items.delete (id)
}
... 

Atualizando itens em nosso banco de dados

A seguir, criaremos uma função chamada markAsPurchased para marcar itens como comprados. Nossa função estará esperando a chave primária do item como seu primeiro argumento quando chamada-neste caso, id . Ele usará essa chave primária para consultar nosso banco de dados para o item que queremos marcar como comprado. Depois de obter o item, ele atualizará sua propriedade markAsPurchased para true :

...
const markAsPurchased=async (id, evento)=> { if (event.target.checked) { aguardar db.items.update (id, {itemHasBeenPurchased: true}) } outro { aguardar db.items.update (id, {itemHasBeenPurchased: false}) }
}
... 

Em nossa função markAsPurchased , estamos usando o parâmetro event para obter o elemento de entrada específico que está sendo clicado por nosso usuário. Se seu valor for verificado , atualizaremos a propriedade itemHasBeenPurchased para true e, se não, false . O método db. [Name_of_object_store].update () espera a chave primária do item como seu primeiro argumento e os novos dados do objeto como seu segundo argumento.

Esta é a aparência de nosso componente App neste estágio:

...
const App=()=> { const allItems=useLiveQuery (()=> db.items.toArray (), []); if (! allItems) retorna nulo const addItemToDb=evento async=> { event.preventDefault () const name=document.querySelector ('. item-name'). value const price=document.querySelector ('. item-price'). value aguarde db.items.add ({nome, preço, itemHasBeenPurchased: falso}) } const removeItemFromDb=async id=> { aguardar db.items.delete (id) } const markAsPurchased=async (id, evento)=> { if (event.target.checked) { aguardar db.items.update (id, {itemHasBeenPurchased: true}) } outro { aguardar db.items.update (id, {itemHasBeenPurchased: false}) } } ...
} 

Agora vamos criar uma variável chamada itemData para hospedar o código JSX para todos os nossos dados de itens:

...
const itemData=allItems.map (({id, nome, preço, itemHasBeenPurchased})=> ( 

$ {price}

removeItemFromDb (id)} className="col s2 material-icons delete-button"> excluir
)) ...

Em nossa variável itemData , estamos mapeando todos os itens na matriz de dados allItems e, em seguida, obtendo as propriedades id , name , price e itemHasBeenPurchased de cada objeto de item. Em seguida, substituímos os dados anteriormente estáticos por nossos novos valores dinâmicos do banco de dados.

Observe que também usamos nossos métodos markAsPurchased e removeItemFromDb como ouvintes de evento de clique em seus respectivos botões. Adicionaremos o método addItemToDb ao evento onSubmit de nosso formulário no próximo bloco.

Com nosso itemData pronto, vamos atualizar a instrução de retorno de nosso componente App para o seguinte código JSX:

...
Retorna ( 

Aplicativo de lista de mercado

addItemToDb (event)}>
{allItems.length> 0 &&
{itemData}
}
) ...

Em nossa declaração de retorno, incluímos a variável itemData em nossa lista de itens. Também usamos o método addItemToDb como o valor onsubmit para nosso add-item-form .

Para testar nosso aplicativo, podemos voltar à página da web do React que abrimos anteriormente. Lembre-se de que seu aplicativo React deve estar em execução. Se não for, execute o comando npm start em seu terminal. Seu aplicativo deve ser capaz de funcionar como a demonstração abaixo:

Testando o recurso Adicionar item

Também podemos usar condições para consultar nosso banco de dados IndexedDB com Dexie. Por exemplo, se quisermos obter todos os itens com preço acima de US $ 10, podemos fazer isso:

 const items=await db.friends .onde ('preço'). acima (10) .toArray (); 

Você pode ver outros métodos de consulta na documentação do Dexie .

Concluindo

Neste artigo, aprendemos como usar IndexedDB para armazenamento offline e como Dexie.js simplifica o processo. Também vimos como usar o gancho Dexie useLiveQuery para observar as alterações e renderizar novamente nosso componente React sempre que o banco de dados for atualizado.

Como IndexedDB é nativo do navegador, consultar e recuperar dados de nosso banco de dados é muito mais rápido do que enviar solicitações de API do lado do servidor sempre que precisamos trabalhar com dados em nosso aplicativo, e podemos armazenar quase tudo em IndexedDB bancos de dados.

O suporte do navegador teria sido um grande problema com o uso de IndexedDB no passado, mas todos os principais navegadores agora o suportam. As muitas vantagens de usar IndexedDB para armazenamento offline em aplicativos da web superam as desvantagens, e usar Dexie.js junto com IndexedDB torna o desenvolvimento da web muito mais interessante do que nunca.

Este é um link para o repositório GitHub para nosso aplicativo de demonstração. Se você gostar, dê uma estrela e me siga no GitHub. Você também pode entrar em contato comigo no LinkedIn se precisar de mais ajuda com a integração de IndexedDB e Dexie.js em aplicativos React.

A postagem Usando Dexie.js em aplicativos React para armazenamento de dados offline apareceu primeiro no LogRocket Blog .

Source link