# Redux 数据仓库的数据持久化和 Next.js 中使用流程 | JustPure 起始页的构建历程

原文：https://blog.grtsinry43.com/posts/just-pure-redux/

作者：grtsinry43

发布时间：2025-03-07T03:54:59Z

内容更新时间：2025-03-07T03:54:59Z

最近写了一个浏览器的起始页，是我无数忙碌任务中拼命抽时间赶出来的，很简陋但也是这段时间的一点心血，在无数忙碌生活中留下一个小小的自己的栖息罢了。

[JustPure 起始页——摒弃繁杂，唯有纯粹](https://www.justpure.dev/)

目前网站位于 cf 全球网络 ~~（国内减速网络）~~，之后我想办法优化回国带宽体验，也会写一个浏览器插件使之直接运行于本地，减少网络开销和卡顿。

## 界面和设计

额，这次就是我大面积用了 shadcn/ui，跟着组件库走还是可以的，有大佬反馈太花哨...我是计划提供切换一个模糊的关闭功能 ~~（其实之前写了但是实在写不完了，那个开关被我注释掉了）~~

![image](https://blog.grtsinry43.com/uploads/2025/03/07/image.png_1a253829-d6bf-4d86-a44b-498d498c2d4f.png)

## Redux 的数据持久化

主要是想讲一下这个，首先是在 Next.js 中可以直接参考官方文档：

[Redux Toolkit Setup with Next.js](https://redux.js.org/usage/nextjs)

我的计划是维护一个数据仓库，并且会实时同步到 localstorage 做数据持久化

思路是这样的：

首先 Redux 提供了一个 `preloadedState` 部分，这允许我们先预先加载一部分数据，我们可以利用这个将 localstorage 的内容加载进来，而对于保存，Redux，提供了一个事件订阅 `subscribe`，我们可以在这里异步进行存储和更新。

思路很明确了，那我们就可以直接开始写了：

加载状态

```typescript
const loadState = () => {
    if (typeof window === 'undefined') {
        return undefined;
    }
    try {
        const serializedState = localStorage.getItem('appState');
        if (serializedState === null) {
            return undefined;
        }
        return JSON.parse(serializedState);
    } catch (err) {
        console.error(err);
        return undefined;
    }
};
```

保存/覆盖状态

```typescript
const saveState = (state: RootState) => {
    if (typeof window === 'undefined') {
        return;
    }
    try {
        const serializedState = JSON.stringify(state);
        localStorage.setItem('appState', serializedState);
    } catch (err) {
        console.error(err);
    }
};
```

于是我们可以在构建 store 中使用这两个函数

```typescript
export const makeStore = () => {
    const store = configureStore({
        reducer: {
            //...（你的 reducers）
        },
        preloadedState: loadState(),
    });

    if (typeof window !== 'undefined') {
        store.subscribe(() => {
            saveState(store.getState());
        });
    }

    return store;
}
```

如果你使用的是 js，那么恭喜你已经没有问题了，但是如果是 ts，...这种写法类型推断就无法正常运行了

原因在哪里呢？

其实很简单我们看函数签名就一目了然了，由于 `JSON.parse` 返回了 `any`，整个函数返回了 `undefined|any`，导致类型与 store 不匹配，那么我们就要着手解决这个问题。

我们先集中定义类型：

```typescript
// 1. 先集中定义所有 reducers
const reducers = {
    //...（你的 reducers）
};
```

然后我们要手动推导 `RootStore` 的类型：

```typescript
// 2. 基于 reducers 推导 RootState 类型
export type RootState = {
    [K in keyof typeof reducers]: ReturnType<typeof reducers[K]>
};
```

拿到了类型就可以在加载数据的时候进行类型断言啦：

```typescript
// 3. 这里解析后断言为 RootState 类型（需确保存储结构与当前 reducers 匹配）
        return JSON.parse(serializedState) as RootState;
```

此时我们组合之前的函数：

```typescript
export const makeStore = () => {
    const store = configureStore({
        reducer: reducers, // 使用集中定义的 reducers
        preloadedState: loadState(), // 现在类型明确为 RootState | undefined
    });

    if (typeof window !== 'undefined') {
        store.subscribe(() => {
            saveState(store.getState());
        });
    }

    return store;
}
```

此时的类型推断就正常啦：

![image](https://blog.grtsinry43.com/uploads/2025/03/07/image.png_c9f2edc7-c5aa-4dd8-96fe-b83e8b61965d.png)

完整代码直接参考我的仓库：https://github.com/grtsinry43/pure-start/blob/main/lib/store.ts

## 谈谈想法

这个是我最近真的是拼命挤出时间写的，连这篇文章也很仓促，无论评价是精美还是花哨我都慢慢去改进，提供自定义和多个用户体验，对我来说就当是熟悉这个组件库了，还是练手的成分更多

---

此项目正处于早期开发阶段，欢迎提出建议和贡献代码

https://github.com/grtsinry43/pure-start
