Intro
Uspójniamy zarządzanie local oraz session storage. Napiszemy fasadę na populare JavaScriptowe API i dodamy type-safety za pomocą TypeScript.
Wstęp
Local storage i session storage to jedne z najczęściej używanych mechanizmów do przechowywania danych w aplikacjach web. Pomijam ciasteczka 🍪, bo mam uczulenie na gluten.
Dziś stworzymy mechanizm, który ujednolici pracę nad tymi szarlatanami, zapewni type safety i sprawi, że odczyt i manipulacja stanie się prostrza!
Dlaczego potrzebujemy wrappera na local/session storage?
Analizując API local/session storage można zauważyć, że ich metody są identyczne - mają taką samą sygnaturę.
Ładowanie
Co rzuca się w oczy w pierwszym momencie?
- Klucz to 'string'
- Odczytana wartość jest typu 'string'
- API są identyczne
- Brak enkapsulacji
Łatwo zrobić literówkę...
Ładowanie
Dodatkowo możemy przez pomyłkę uruchomić ten kod po stronie serwera, co nie ma szansy zadziałać - obydwa API są dostępne tylko w środowisku przeglądarki.
Ładowanie
Wiadomość błędu niekoniecznie może naprowadzić na przyczynę błędu. Jest to problem zwłaszcza dla początkujących Frątasiów.
A co z odczytem i zapisem?
Za każdym razem musimy robić powtarzalną logikę - parsowanie przy odczycie i zapisie.
Ładowanie
Czas na ostatni frykas! A co z mockowaniem przy testowaniu? No, czeka nas ta sama duplikacja logiki i brak silnego typowania.
Ładowanie
Jak widzisz jest tego sporo, a pominąłem jeszcze takie rzeczy jak:
- A co jak potrzebujemy dodatkowej metody?
- Brak enkapsulacji
- Singleton na całej aplikacji - łatwo popsuć inną funkcjonalność
- Można doprowadzić do kolizji - nadpisać wartość ustawioną w innej funkcjonalności
Musimy to naprawić!
Drobna inspiracja na początek
Pamiętasz bibliotekę Axios? Zapewne tak! Tworzymy w niej instancję obiektu, który w oparciu o przekazaną konfigurację zwraca Ci konkretne, spójne i wygodne w użyciu API. Chcemy uzyskać taki sam efekt.
W Axios robimy tak:
Ładowanie
A nasza drobna biblioteczka zrobi tak:
Ładowanie
Tu wywołanie metod:
Ładowanie
Spójrz na poniższy gif i zobacz jaka jest docelowa idea:
Ładowanie
TypeScript już nas odpowiednio przypilnuje
Modelujemy local/session storage i piszemy testy
Na początek stwórzmy sobie string literal type, w którym zdefiniujemy wspierane storage oraz dodamy szkielet funkcji storage.
Ładowanie
Teraz testy do implementacji, której jeszcze nie ma. Testy będą czerwone, dopóki nie napiszemy kodu, który sprawi, że zaczną być zielone - skorzystamy z TDD.
Jeżeli chcesz zrozumieć, czym jest test driven development, chcesz zgłębić tajniki testowania, to zapraszam do tego artykułu oraz następującego kursu.
Ładowanie
Ładowanie
Ładowanie
Ładowanie
No i na sam koniec test, który weryfikuje działanie kilku metod wywoływanych po sobie - tak jak będzie się to odbywać w rzeczywistości.
Ładowanie
Czas na implementację - czyli sprawiamy, że nasze testy będą zielone
No dobra, mamy modele, testy i wiemy, jak nasze API będzie wyglądać/działać - teraz czas na implementację, czyli to, co Frątasie, tacy jak my, kochają najbardziej.
Zaczniemy od funkcji, która rzuci wyjątek z odpowiednim komunikatem, jeżeli local/session storage nie będzie zdefiniowane w obiekcie globalnym - przykładowo uruchomiliśmy naszą funkcję po stronie serwera.
Ładowanie
Następnie dodamy implementację metody do odczytu pojedynczej wartości: get. Zwróć uwagę na rzutowanie, które jest niezbędne - metoda JSON.parse nie pozwala na przekazanie generycznego parametru.
Ładowanie
Czas na getAll, która ma za zadanie zwrócić wszystkie ustawione wartości. Zwróć uwagę na Record i jego "dziwne" typowanie. Chcemy, aby zwrócony obiekt miał takie same klucze jak przekazany interfejs oraz odpowiadające im wartości.
Ładowanie
W tym momencie mamy dwa testy, które są zielone. Oznacza to, że rzucanie wyjątków i odczyt pojedynczej wartości oraz wielu wartości działa prawidłowo.
- throws an error if any storage is undefined ✔️
- allows to get all values ✔️
Teraz czas na getKeys. Tu niespodzianki nie ma. Po prostu zwracamy wartość keys, do której mamy dostęp dzięki domknięciu (closure).
Ładowanie
Dlaczego jest to funkcja, a nie po prostu keys: keys. Musi to być funkcja, która odczyta "najnowsze" wartości. Jeżeli byłaby to zmienna - to wartość była by zawsze taka sama - początkowo ustawionych kluczy (pusta tablica).
Po tej zmianie kolejny test staje się zielony.
- picks keys from local storage or session storage ✔️
Teraz czas na ostatnie cztery metody: remove, set, patch oraz clear.
Ładowanie
W remove usuwamy wartość z wybranego local/session storage, a następnie pozbywamy się przekazanego klucza z tablicy.
W set ustawiamy wartość oraz dodajemy klucz. Jednocześnie sprawdzamy, czy klucz już istnieje. Jeżeli tak jest, to nie dodajemy tego samego - po co nam duplikaty?
W clear czyścimy wszystko, co zostało kiedykolwiek ustawione - nie całe local/session storage - to mogłoby wpłynąć na negatywnie na inne funkcjonalności.
W patch dodajemy tyle wartości, ile przekazaliśmy kluczy w obiekcie. Jednocześnie sprawdzamy, czy przekazana wartość nie jest przypadkiem undefined - to spowoduje wyjątek w JSON.parse przy próbie odczytu. Dlatego pomijamy takie wartości.
Po tych wszystkich zmianach w kodzie nasze testy są zielone jak Shrek.
Skończony kod
W tym repozytorium znajdziesz skończone rozwiązanie.
Skończony przykład
Jeżeli chcesz wykorzystać omówioną implementację, to wykorzystaj to repozytorium. Poniżej masz skończony przykład w postaci snippetów z kodem.
Ładowanie
Ładowanie
Ładowanie
Podsumowanie
Jeszcze raz na spokojnie co udało nam się osiągnąć 🧂:
- Mamy developer friendly wyjątki
- Zarządzanie local/session storage jest modułowe
- Mamy type-safety i ochronę przed literówkami
- Nie ma potrzeby zapamiętywania kluczy w zmiennych
- Łatwo możemy dodawać nowe metody i rozszerzać rozwiązanie
- Rozwiązanie może być wykorzystywane do mockowania wartości również w testach
- Spójne API
- Mniejsze ryzyko kolizji lub nadpisania wartości w innej funkcjonalności
Warto wspomnieć, że wprowadzanie abstrakcji takiej jak ta, nie zawsze ma sens. W tym przypadku jest to użyteczne, ale warto przeprowadzić porównanie po zakończonej pracy, jaki jest zysk i rezultat. Może nie było to potrzebne?
Według mnie nie jest to niezbędne, ale ułatwia życie.
Komentarze
Przejrzyj komentarze artykułu i dodaj swoją opinię.