You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用RecoilJS设计支持离线功能的博客应用状态?

RecoilJS 博客应用状态设计与实现方案

一、状态设计方案

针对你的API结构,推荐按以下方式拆分状态:

1. 分页帖子列表

  • 用**atom**存储分页控制参数(当前页码、每页数量),这类全局共享的可写状态适合用atom管理。
  • 用**selector**封装分页帖子的API请求逻辑,组件直接通过selector获取数据,无需处理请求细节。
// 分页控制Atom
const postsPageState = atom({
  key: 'postsPageState',
  default: { page: 1, limit: 10 },
});

// 获取分页帖子列表的Selector
const postsListSelector = selector({
  key: 'postsListSelector',
  get: async ({ get }) => {
    const { page, limit } = get(postsPageState);
    const res = await fetch(`/posts?page=${page}&limit=${limit}`);
    return res.json();
  },
});

2. 单篇完整帖子

  • 用**atomFamily**存储单篇帖子的完整内容,通过帖子ID作为索引,实现独立缓存每个帖子的数据。
  • 用**selectorFamily**封装单篇帖子的API请求逻辑,先检查缓存(atomFamily),无数据时再发起请求并更新缓存。
// 单篇帖子详情AtomFamily
const postDetailState = atomFamily({
  key: 'postDetailState',
  default: null, // 默认未加载
});

// 获取单篇帖子的SelectorFamily
const postDetailSelector = selectorFamily({
  key: 'postDetailSelector',
  get: (postId) => async ({ get, set }) => {
    let post = get(postDetailState(postId));
    if (!post) {
      const res = await fetch(`/post/${postId}`);
      post = await res.json();
      // 更新缓存
      set(postDetailState(postId), post);
    }
    return post;
  },
});

状态选型原则

  • atom/atomFamily:用于存储需要缓存、持久化或可修改的状态(如分页参数、已获取的帖子详情)。
  • selector/selectorFamily:用于封装只读、依赖其他状态或异步获取的逻辑(如API请求、数据转换)。

二、localStorage持久化实现

方案1:使用RecoilPersist插件

官方插件可快速实现状态持久化,自动同步atom到localStorage:

import { recoilPersist } from 'recoil-persist';

const { persistAtom } = recoilPersist({
  key: 'blog-recoil-store', // localStorage存储的键前缀
  storage: localStorage,
});

// 给需要持久化的Atom添加effects
const postsPageState = atom({
  key: 'postsPageState',
  default: { page: 1, limit: 10 },
  effects_UNSTABLE: [persistAtom],
});

const postDetailState = atomFamily({
  key: 'postDetailState',
  default: null,
  effects_UNSTABLE: [persistAtom],
});

方案2:自定义持久化逻辑

适合需要自定义同步规则的场景,手动在Atom的effects中处理localStorage读写:

const postDetailState = atomFamily({
  key: 'postDetailState',
  default: null,
  effects_UNSTABLE: (postId) => [
    ({ setSelf, onSet }) => {
      // 初始化时从localStorage读取
      const savedPost = localStorage.getItem(`post-${postId}`);
      if (savedPost) setSelf(JSON.parse(savedPost));
      // 状态更新时写入localStorage
      onSet((newVal) => {
        newVal 
          ? localStorage.setItem(`post-${postId}`, JSON.stringify(newVal))
          : localStorage.removeItem(`post-${postId}`);
      });
    },
  ],
});

三、组件使用示例

帖子列表组件

import { useRecoilState, useRecoilValueLoadable } from 'recoil';

function PostsList() {
  const [pageState, setPageState] = useRecoilState(postsPageState);
  const postsLoadable = useRecoilValueLoadable(postsListSelector);

  switch (postsLoadable.state) {
    case 'loading':
      return <div>加载中...</div>;
    case 'hasError':
      return <div>加载失败:{postsLoadable.error.message}</div>;
    case 'hasValue':
      return (
        <div>
          {postsLoadable.contents.map(post => (
            <div key={post.id}>
              <h3>{post.title}</h3>
              <p>{post.excerpt}</p>
              <a href={`/post/${post.id}`}>查看详情</a>
            </div>
          ))}
          <button onClick={() => setPageState({...pageState, page: pageState.page + 1})}>
            下一页
          </button>
        </div>
      );
  }
}

帖子详情组件

import { useRecoilValueLoadable } from 'recoil';

function PostDetail({ postId }) {
  const postLoadable = useRecoilValueLoadable(postDetailSelector(postId));

  switch (postLoadable.state) {
    case 'loading':
      return <div>加载中...</div>;
    case 'hasError':
      return <div>加载失败:{postLoadable.error.message}</div>;
    case 'hasValue':
      const post = postLoadable.contents;
      return (
        <div>
          <h1>{post.title}</h1>
          <div className="post-content">{post.content}</div>
        </div>
      );
  }
}

四、代码结构建议

推荐按以下目录组织状态逻辑,实现关注点分离:

  • src/state/atoms.js:所有Atom/AtomFamily定义
  • src/state/selectors.js:所有Selector/SelectorFamily定义
  • src/state/persist.js:持久化相关逻辑封装
  • src/components/:所有UI组件

内容的提问来源于stack exchange,提问作者Jeremy Bernier

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 19:55:13