Persistent Cache Implementation in Vue with TypeScript
Building a persistent cache in a Vue application can significantly improve performance by reducing redundant API calls and providing a faster user experience. This challenge asks you to implement a simple persistent cache using Vue's reactivity system and localStorage to store cached data, ensuring data persists across browser sessions.
Problem Description
You need to create a usePersistentCache composable function in Vue.js (using TypeScript) that manages a cache for data. This cache should:
- Store data in
localStorage: The cached data should be stored in the browser'slocalStorageunder a specified key. - Provide a reactive ref: The composable should return a reactive
refcontaining the cached data. Changes to thisrefshould trigger updates in the component using the composable. - Handle initial loading: When the composable is first used, it should attempt to retrieve data from
localStorage. If data is found, it should be loaded into the reactiveref. If not, therefshould be initialized asnullor an appropriate default value. - Allow data updates: The composable should provide a function to update the cached data. When this function is called, the new data should be stored in
localStorageand the reactiverefshould be updated. - Handle errors gracefully: If there's an error accessing
localStorage(e.g.,localStorageis disabled), the composable should log the error and continue to function, initializing therefwith a default value (e.g.,null).
Expected Behavior:
- The first time the composable is used, it checks
localStorage. If the key exists, the data is loaded into the reactiveref. Otherwise, therefis initialized tonull. - When data is updated using the provided function, the new data is stored in
localStorageand the reactiverefis updated immediately. - Subsequent uses of the composable will load data from
localStorageif the key exists. - If
localStorageis unavailable, the composable should log an error and initialize thereftonull.
Examples
Example 1:
Input: usePersistentCache('user_data', () => fetch('/api/user').then(res => res.json()))
Output: { data: { name: 'John Doe', email: 'john.doe@example.com' }, updateData: (newData: any) => { ... } }
Explanation: The composable fetches user data from the API, stores it in localStorage under the key 'user_data', and provides a reactive ref for the data. Subsequent calls will load from localStorage.
Example 2:
Input: usePersistentCache('settings', { theme: 'light' })
Output: { data: { theme: 'light' }, updateData: (newData: any) => { ... } }
Explanation: The composable initializes the cache with the provided settings object. This object is stored in localStorage under the key 'settings'.
Example 3: (localStorage disabled)
Input: usePersistentCache('cart', () => []) // localStorage is disabled
Output: { data: null, updateData: (newData: any) => { ... } }
Explanation: Because localStorage is disabled, the composable logs an error and initializes the data ref to null.
Constraints
- Data Size: Assume the data stored in
localStoragewill be relatively small (less than 5MB). WhilelocalStoragecan store more, excessively large data can impact performance. - Key Format: The cache key should be a string.
- Error Handling: The composable must handle errors related to
localStorageaccess gracefully. - Performance: The composable should be efficient and avoid unnecessary re-renders. Use Vue's reactivity system effectively.
- TypeScript: The solution must be written in TypeScript.
Notes
- Consider using
JSON.stringifyandJSON.parseto serialize and deserialize data when storing and retrieving fromlocalStorage. - Think about how to handle asynchronous data fetching within the composable.
- The
updateDatafunction should update both thelocalStorageand the reactiveref. - Focus on creating a reusable and well-structured composable function.
- You don't need to implement the API fetching logic itself; the composable should accept a function that returns a promise resolving to the data. This allows for flexibility in how the data is obtained.