使用Recoil时如何对深层嵌套的复杂数据结构做原子化拆分?
深层嵌套结构Recoil原子化拆分最佳实践
1. 先判断是否需要过度拆分
拆分atom的核心前提是:子节点需要被独立订阅、独立修改,或者被多个父节点共享。如果你的tag仅属于单个todo、不会被其他todo复用,也不会有单独修改tag后所有关联todo自动更新的需求,完全不需要把tag拆成独立atomFamily,直接把tag数组存在对应todo的atom里即可,从根源上避免额外的操作复杂度。
2. 封装统一的原子操作层,屏蔽拆分细节
如果确实需要细粒度拆分,不要在业务代码里手写零散的atom操作,把所有CRUD逻辑封装成自定义Hook,内部处理所有关联原子的更新:
举个新增todo的封装示例:
const useCreateTodo = () => { const setTodos = useSetRecoilState(todosAtom) const setTodo = useSetRecoilState(todoAtomFamily) const setTag = useSetRecoilState(tagAtomFamily) return (rawTodo) => { // 生成唯一id,可使用uuid或者自定义自增策略 const todoId = generateId() // 批量处理tags const tagIds = rawTodo.tags.map(rawTag => { const tagId = generateId() setTag(tagId, {text: rawTag.text}) return tagId }) // 写入todo atom setTodo(todoId, { done: rawTodo.done, text: rawTodo.text, tags: tagIds }) // 更新todo列表 setTodos(prev => [...prev, todoId]) } }
业务层调用时直接传入原始的嵌套todo对象即可,完全不用感知内部拆分逻辑,删除、更新操作也按照同样思路封装即可。如果tag是共享的,还可以在封装逻辑里加去重判断,避免创建重复的tag atom。
3. 用selectorFamily做数据聚合,避免业务层手动拼接
需要使用完整嵌套结构的场景,统一通过selector导出,业务组件不用自己逐个拉取关联的atom:
const todoWithTagsSelectorFamily = selectorFamily({ key: 'todoWithTags', get: (todoId) => ({get}) => { const todo = get(todoAtomFamily(todoId)) const tags = todo.tags.map(tagId => get(tagAtomFamily(tagId))) return {...todo, tags} } })
4. 复杂关联场景增加关系表原子
如果嵌套层级多、跨节点关联复杂,可以额外加一个关系映射atom,用来存储子节点的引用关系,比如:
// 存储每个tag被哪些todo引用 const tagRefsAtom = atom({ key: 'tagRefs', default: {} })
删除todo时直接查这个关系表就能拿到所有关联的tag,同时判断引用计数,如果某个tag的引用数归0再执行删除,避免误删其他todo正在使用的tag。
5. 批量操作用事务API合并更新
涉及多个原子的批量更新,用Recoil提供的useRecoilTransaction_UNSTABLE来合并提交,避免多次触发重渲染,也能简化批量setter的写法。
内容的提问来源于stack exchange,提问作者Yoav Kadosh
相关产品推荐
相关产品推荐

