TypeScript와 StorageManager

TypeScript와 StorageManager

이 글은 TypeScript와 StorageManager에 대해 설명합니다.

TypeScript와 StorageManager에 대해 실제 예제와 함께 설명합니다.

YouTube Video

TypeScript와 StorageManager

StorageManager란 무엇인가요?

StorageManager는 웹 애플리케이션이 사용 중인 스토리지와 사용 가능한 용량을 추정할 수 있게 해주는 API입니다. 또한 사용자가나 브라우저에 의한 자동 데이터 삭제를 방지하는 지속성 요청(persist()) 기능도 제공합니다. 이를 통해 오프라인 애플리케이션에서 IndexedDB에 대용량 데이터를 저장할 때 충분한 여유 공간이 있는지 또는 데이터가 자동으로 삭제될 수 있는지 제어할 수 있습니다.

기능 감지

먼저 브라우저가 navigator.storage를 지원하는지 확인하세요.

 1// TypeScript: feature detection for StorageManager
 2function supportsStorageManager(): boolean {
 3  return typeof navigator !== "undefined" &&
 4         typeof navigator.storage !== "undefined" &&
 5         typeof navigator.storage.estimate === "function";
 6}
 7
 8// Example usage
 9if (supportsStorageManager()) {
10  console.log("StorageManager is supported.");
11} else {
12  console.warn("StorageManager not supported. Falling back to localStorage or IndexedDB.");
13}
  • 이 함수는 StorageManager API의 사용 가능 여부를 단계적으로 확인합니다.
  • Safari와 같은 일부 브라우저에서 navigator.storage가 존재하지 않기 때문에 typeof를 사용하여 안전하게 존재 여부를 확인하는 것이 중요합니다.

스토리지 용량 추정하기 (estimate())

supportsStorageManager()로 기능을 확인한 후, navigator.storage.estimate()에서 사용량(usage)과 할당량(quota)을 가져옵니다.

 1// TypeScript: safely get storage estimate
 2async function getStorageEstimate(): Promise<{ usage: number; quota: number } | null> {
 3  if (!supportsStorageManager()) {
 4    console.warn("StorageManager not supported.");
 5    return null;
 6  }
 7
 8  const estimate = await navigator.storage.estimate();
 9  return { usage: estimate.usage ?? 0, quota: estimate.quota ?? 0 };
10}
11
12// Example usage
13getStorageEstimate().then(result => {
14  if (result) {
15    console.log(`Usage: ${result.usage} bytes / Quota: ${result.quota} bytes`);
16  }
17});
  • 이 함수는 항상 안전하게 동작하며, 브라우저가 지원하지 않을 경우에는 null을 반환합니다.
  • usagequota는 추정치이므로 브라우저마다 다를 수 있습니다.

데이터 지속성 요청 (persist())

persist()를 사용하여 중요한 데이터(예: 오프라인 캐시)가 브라우저에 의해 자동으로 삭제되지 않도록 지속성을 요청할 수 있습니다. 하지만 모든 환경에서 항상 성공하는 것은 아닙니다.

 1// TypeScript: safely request persistent storage
 2async function requestPersistence(): Promise<boolean> {
 3  if (!supportsStorageManager()) {
 4    console.warn("StorageManager not supported.");
 5    return false;
 6  }
 7
 8  if (typeof navigator.storage.persist !== "function") {
 9    console.warn("persist() not available in this browser.");
10    return false;
11  }
12
13  try {
14    const granted = await navigator.storage.persist();
15    return Boolean(granted);
16  } catch (err) {
17    console.error("persist() error:", err);
18    return false;
19  }
20}
21
22// Example usage
23requestPersistence().then(granted => {
24  console.log("Persistence granted?", granted);
25});
  • persist()가 성공하면 사용자가 직접 삭제하지 않는 한 데이터가 브라우저에 의해 자동으로 삭제되지 않습니다. 하지만 사용자 동작이나 브라우저 설정에 따라 요청이 거절될 수 있습니다.

데이터 저장 전 사용 가능한 스토리지 확인

대용량 데이터를 저장하기 전에 사용 가능한 공간을 확인하여 쓰기 실패(QuotaExceededError)를 방지하세요.

 1// TypeScript: ensure enough space before writing
 2async function ensureSpaceAndWrite(neededBytes: number, writeFn: () => Promise<void>): Promise<boolean> {
 3  const estimate = await getStorageEstimate();
 4  if (!estimate) {
 5    console.warn("Cannot check storage space. Proceeding without validation.");
 6    await writeFn();
 7    return true;
 8  }
 9
10  const free = estimate.quota - estimate.usage;
11  if (free < neededBytes) {
12    console.warn(`Not enough space. Free: ${free} bytes, needed: ${neededBytes} bytes.`);
13    return false;
14  }
15
16  await writeFn();
17  return true;
18}
19
20// Example usage
21ensureSpaceAndWrite(10 * 1024 * 1024, async () => {
22  console.log("Saving large data...");
23});
  • 저장 전에 사용 가능한 스토리지를 확인하면 용량 부족으로 인한 쓰기 중단 위험을 줄일 수 있습니다.

TypeScript에서 localStorage 타입 안전하게 다루기

localStorage는 가벼운 설정 데이터나 비슷한 정보를 저장하는 데 편리합니다.

다음 클래스는 제네릭을 사용하여 타입 안정 래퍼를 만듭니다.

 1// TypeScript: typed localStorage wrapper
 2type Serializer<T> = {
 3  serialize: (v: T) => string;
 4  deserialize: (s: string) => T;
 5};
 6
 7class TypedLocalStorage<K extends string, V> {
 8  constructor(private storage: Storage, private serializer: Serializer<V>) {}
 9
10  set(key: K, value: V): void {
11    this.storage.setItem(key, this.serializer.serialize(value));
12  }
13
14  get(key: K): V | null {
15    const raw = this.storage.getItem(key);
16    if (raw === null) return null;
17    try {
18      return this.serializer.deserialize(raw);
19    } catch {
20      return null;
21    }
22  }
23
24  remove(key: K): void {
25    this.storage.removeItem(key);
26  }
27
28  clear(): void {
29    this.storage.clear();
30  }
31}
32
33// Example usage
34const jsonSerializer: Serializer<any> = {
35  serialize: v => JSON.stringify(v),
36  deserialize: s => JSON.parse(s),
37};
38
39const appStorage = new TypedLocalStorage<'theme' | 'token', any>(localStorage, jsonSerializer);
40appStorage.set('theme', { dark: true });
41console.log(appStorage.get('theme'));
  • 이 클래스는 잘못된 타입으로 데이터를 저장하거나 가져올 위험을 줄여줍니다.

실용적인 고려사항 및 모범 사례

  • 브라우저가 StorageManager를 지원하는지 항상 확인하세요 StorageManager는 비교적 새로운 API이므로 사용 전에 항상 존재 여부를 확인해야 합니다.

  • 스토리지 한도 및 호환성에 주의하세요 estimate()로 반환되는 값은 브라우저마다 다르며 추정치일 뿐입니다.

  • 비동기 API를 고려하여 설계하기 estimate()persist()는 Promise 기반의 비동기 API입니다. UI가 차단되지 않도록 코드를 설계하세요.

  • 보안 고려사항 localStorage에 액세스 토큰과 같은 민감한 정보를 저장하지 마세요. 가능하다면 HttpOnly 쿠키나 암호화를 사용하는 것을 고려하세요.

  • 폴백 설계 supportsStorageManager()가 false를 반환하는 경우 localStorage 또는 IndexedDB만 사용하는 로직으로 전환하세요.

요약

supportsStorageManager()로 기능 감지를 구현하면 모든 환경을 안전하게 지원할 수 있고, navigator.storage.estimate()로 사용량과 쿼터를 확인하며, persist()로 지속성을 요청할 수 있습니다. TypeScript에서는 래퍼 함수와 타입 정의를 사용하여 읽기 쉽고 안전한 코드를 작성하세요. 기본 흐름은 존재 여부 확인, 용량 추정, 지속성 요청, 저장 전 용량 확인 순입니다.

위의 기사를 보면서 Visual Studio Code를 사용해 우리 유튜브 채널에서 함께 따라할 수 있습니다. 유튜브 채널도 확인해 주세요.

YouTube Video