🐥

【TypeScript】ローカルストレージにデータを保持するカスタムフックの設計

2024/06/02に公開

概要

本記事では、TypeScriptを用いてデータの保存方法を抽象化し、SessionStorage、LocalStorage、Cookieの3つの異なるストレージに対応可能なStore Managerの実装方法について紹介します。また、Reactのカスタムフックを使って、これらのストレージと連携する方法も説明します。

Store Managerの実装

データの管理方法が変更されても対応できるように、共通のインターフェースStoreManagerを定義し、それぞれのストレージに対する具体的な実装を行います。

インターフェースとファクトリ関数の定義

// 共通のインターフェースにしてデータの管理方法変更時の開発工数を低減させる
interface StoreManager<T> {
  get: () => T | null;
  set: (value: T) => void;
}
interface StoreManagerConstructor {
  <T>(key: string, options: { expires: number }): StoreManager<T>;
}

SessionStorageの実装

const createSessionStorageManager: StoreManagerConstructor = <T>(
  key: string,
  options: { expires: number }
) => {
  const get = () => {
    try {
      const value = sessionStorage.getItem(key);
      if (!value) {
        return null;
      }
      const parsed = JSON.parse(value) as { value: T; createdAt: number };
      if (parsed.createdAt + options.expires < Date.now()) {
        return null;
      }
      return parsed.value;
    } catch (error) {
      console.error(error);
      return null;
    }
  };

  const set = (value: T) => {
    sessionStorage.setItem(
      key,
      JSON.stringify({
        value,
        createdAt: Date.now(),
      })
    );
  };

  return { get, set };
};

LocalStorageの実装

const createLocalStorageManager: StoreManagerConstructor = <T>(
  key: string,
  options: { expires: number }
) => {
  const get = () => {
    try {
      const value = localStorage.getItem(key);
      if (!value) {
        return null;
      }
      const parsed = JSON.parse(value) as { value: T; createdAt: number };
      if (parsed.createdAt + options.expires < Date.now()) {
        return null;
      }
      return parsed.value;
    } catch (error) {
      console.error(error);
      return null;
    }
  };

  const set = (value: T) => {
    localStorage.setItem(
      key,
      JSON.stringify({
        value,
        createdAt: Date.now(),
      })
    );
  };

  return { get, set };
};

Cookieの実装

const createCookieManager: StoreManagerConstructor = <T>(
  key: string,
  options: { expires: number }
) => {
  const get = () => {
    try {
      const value = document.cookie
        .split('; ')
        .find((item) => item.startsWith(`${key}=`));
      if (!value) {
        return null;
      }
      const parsedValue = JSON.parse(decodeURIComponent(value.split('=')[1])) as T;
      return parsedValue;
    } catch (error) {
      console.error(error);
      return null;
    }
  };

  const set = (value: T) => {
    const expiresDate = new Date(Date.now() + options.expires).toUTCString();
    document.cookie = `${key}=${encodeURIComponent(JSON.stringify(value))}; expires=${expiresDate}; path=/`;
  };

  return { get, set };
};

Store Managerのファクトリ関数

export const StoreType = {
  SESSION_STORAGE: 'sessionStorage',
  LOCAL_STORAGE: 'localStorage',
  COOKIE: 'cookie',
} as const;
type StoreType = (typeof StoreType)[keyof typeof StoreType];

export const createStoreManger = <T>(
  type: StoreType,
  key: string,
  options: { expires: number }
) => {
  switch (type) {
    case StoreType.SESSION_STORAGE:
      return createSessionStorageManager<T>(key, options);
    case StoreType.LOCAL_STORAGE:
      return createLocalStorageManager<T>(key, options);
    case StoreType.COOKIE:
      return createCookieManager<T>(key, options);
  }
}

React Hooksによるカスタムフックの実装

カスタムフックuseStoreManagerを用いて、ストレージと連携するための簡便な方法を提供します。

カスタムフックの実装

const useStoreManger = <T>(...args: Parameters<typeof createStoreManger>) => {
  const [value, _setValue] = useState<T | null>(null);

  const manager = useMemo(() => {
    return createStoreManger<T>(...args);
  }, [...args]);

  useEffect(() => {
    _setValue((prev) => prev || manager.get());
  }, [manager]);

  const setValue = (value: T) => {
    manager.set(value);
    _setValue(value);
  };

  return [value, setValue] as const;
};

カスタムフックの使用例

それぞれのストレージタイプに対する使用例を以下に示します。

SessionStorageの使用例

const useSample1 = () => {
  const [value, setValue] = useStoreManger<{ userName: string }>(
    StoreType.SESSION_STORAGE,
    'user',
    {
      expires: 1000 * 60 * 60 * 24, // 1日
    }
  );

  // いろんな処理...

  return { value, setValue };
};

LocalStorageの使用例

const useSample2 = () => {
  const [value, setValue] = useStoreManger<{ userName: string }>(
    StoreType.LOCAL_STORAGE,
    'user',
    {
      expires: 1000 * 60 * 60 * 24, // 1日
    }
  );

  // いろんな処理...

  return { value, setValue };
};

Cookieの使用例

const useSample3 = () => {
  const [value, setValue] = useStoreManger<{ userName: string }>(
    StoreType.COOKIE,
    'user',
    {
      expires: 1000 * 60 * 60 * 24, // 1日
    }
  );

  // いろんな処理...

  return { value, setValue };
};

まとめ

今回の実装では、データの保存方法を抽象化することで、将来的な変更にも対応可能な設計を行いました。また、Reactのカスタムフックを用いることで、ストレージとの連携が簡単に行えるようにしました。この記事が、皆さんの開発に役立つことを願っています。

関連記事

Discussion