如何用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
相关产品推荐
相关产品推荐

