如何将`(env)=> (args)=>TaskEither`转为ReaderTaskEither优化SPA缓存清理?
重构标签创建逻辑:用ReaderTaskEither优化环境对象传递
问题背景
在SPA中现有标签创建流程:
- 定义
UserId、User、TagType(可选值"NEED_HELP"/"NEED_STORAGE")、Tag、TagDraft等类型 - 通过
createTagDraft生成标签草稿,postTag提交至API,再用flow组合为createTagTask(返回TaskEither)
现在需要新增客户端标签缓存清理操作,当前通过createTagAndCleanTask直接传入queryCache实现,但希望改用ReaderTaskEither(RTE)优化环境对象传递,遇到以下问题:
- 使用
RTE.fromTaskEither处理createTagTask时失败,因为createTagTask是返回TaskEither的函数,而非TaskEither实例 - 在
flow中加入RTE.fromTaskEither时,无法正确配置类型与环境对象 - 不清楚如何通过
flow将(env) => (args) => body的参数顺序反转为(args) => (env) => body
解决方案
1. 明确环境类型定义
首先定义包含queryCache的环境类型,让类型系统清晰约束环境对象:
import { ReaderTaskEither as RTE, TaskEither as TE } from 'fp-ts/lib'; import { QueryCache } from '@tanstack/react-query'; // 假设使用React Query的QueryCache // 定义环境类型 type TagEnv = { queryCache: QueryCache; }; // 定义标签创建的入参类型 type CreateTagArgs = { user: User; type: TagType; };
2. 重构现有逻辑为RTE兼容形式
处理原有createTagTask
原有createTagTask不依赖环境,只需将其返回的TaskEither包装为RTE(忽略环境参数):
// 原有生成草稿逻辑 const createTagDraft = (user: User, type: TagType): TagDraft => { return { userId: user.id, type, createdAt: new Date() }; // 示例实现 }; // 原有提交API逻辑 const postTag = (draft: TagDraft): TE<ApiError, Tag> => { // 示例:调用API并转为TaskEither return TE.tryCatch( () => fetch('/api/tags', { method: 'POST', body: JSON.stringify(draft) }).then(res => res.json()), (err) => err as ApiError ); }; // 原有组合逻辑:返回TaskEither const createTagTask = (args: CreateTagArgs): TE<ApiError, Tag> => flow( ({ user, type }) => createTagDraft(user, type), postTag )(args); // 转为RTE:接受入参,返回不依赖环境的RTE const createTagRTE = (args: CreateTagArgs): RTE<TagEnv, ApiError, Tag> => RTE.fromTaskEither(createTagTask(args));
实现缓存清理的RTE操作
缓存清理依赖环境中的queryCache,直接用RTE.asks获取环境并执行操作:
// 清理标签缓存的RTE操作 const cleanTagCache = (tag: Tag): RTE<TagEnv, never, void> => RTE.asksIO((env) => { // 示例:失效指定用户的标签查询缓存 env.queryCache.invalidateQueries({ queryKey: ['tags', tag.userId] }); });
3. 组合创建与缓存清理逻辑
使用RTE的chainFirst组合两个操作(创建标签后清理缓存,保留标签结果):
// 最终组合逻辑:接受入参,返回包含环境依赖的RTE const createTagAndCleanRTE = (args: CreateTagArgs): RTE<TagEnv, ApiError, Tag> => flow( createTagRTE, RTE.chainFirst(cleanTagCache) // 创建成功后执行缓存清理,保留标签结果 )(args);
4. 解决参数反转问题
如果原有代码是(env) => (args) => TaskEither的形式,可使用fp-ts的flip函数直接反转参数顺序,得到(args) => (env) => TaskEither(即RTE类型):
import { flip } from 'fp-ts/lib/function'; // 原有旧版逻辑:先传环境,再传入参 const oldCreateAndClean = (env: TagEnv) => (args: CreateTagArgs): TE<ApiError, Tag> => TE.chainFirst(createTagTask(args), (tag) => TE.fromIO(() => env.queryCache.invalidateQueries(['tags', tag.userId])) ); // 反转参数顺序:得到(args) => (env) => TE,即RTE<TagEnv, ApiError, Tag> const createTagAndCleanRTE = flip(oldCreateAndClean);
5. 执行RTE逻辑
调用时传入入参和环境对象,执行最终的TaskEither:
// 示例:获取当前环境与入参 const currentEnv: TagEnv = { queryCache: window.queryCache }; const currentArgs: CreateTagArgs = { user: currentUser, type: "NEED_HELP" }; // 执行RTE createTagAndCleanRTE(currentArgs)(currentEnv) .fold( (error) => console.error('标签创建失败:', error), (tag) => console.log('标签创建成功并清理缓存:', tag) )();
关键注意事项
ReaderTaskEither的本质是(env: E) => TaskEither<L, R>,所以只要函数符合这个签名,就是合法的RTE,无需额外包装- 使用
flip函数时,确保原函数是柯里化的(即(a) => (b) => c的形式),否则无法正确反转参数 - 组合RTE操作时,优先使用
chain/chainFirst/map等RTE内置方法,而非直接嵌套flow,避免类型推断问题
内容的提问来源于stack exchange,提问作者xav
相关产品推荐
相关产品推荐

