从 Recoil 到 Jotai (上)

背景

偶然的一次项目开发中发现 Recoil 有内存泄漏的情况,一次几 M ,虽然影响不大,但是还是具有一定的隐患,特别是随着项目的体量增加,后期带来的替换成本也会随之增高;

当然这是原因之一,下面会跟大家聊聊 Recoil 的现状。

现状

  • Recoil 仍旧处于实验性质
  • Meta 官方宣布将 Recoil 开放给外部维护者;(来自 Discord,尚未证实)
  • 核心开发者 drarmstr 已于 20231 月被解雇;(已证实LinkedIn
  • FaceBook 官网仍在使用 Recoil,并且将其作为生产应用来使用;

其实抛开上述现状来讲,内部项目在生产使用 Recoil 并无什么大的问题,偶现场景的内存泄漏,基本属于极端场景,但是基于长远发展的方向而言,库的选型上可能略有不妥;

但是不的不吐槽 Recoil 的包体积确实很大,这也是我下决心替换库的最大动机。


Recoil VS Jotai

参考:

Jotai 相比较 Recoil 主要有几个区别:

  • atom 作为 key 比起 唯一string key,样板代码变少了、内存泄漏风险减少了;
  • 包体积更小;
  • API 更简单容易理解,可拔插、集成能力更优异;
  • 维护者更积极,BUG & Feature 响应更快;
  • 性质不同,一个是稳定性质一个是实验性质;

Jotai

新的 原子型 状态管理库的后备方案;

核心维护者是 Dai-shi,目前有 14.9k+ 星星,受众公司也蛮多;


聊到这里,咱们可以步入正题了,我准备从 原子型 状态的思想、recoiljotaiAPI 迁移指南入题讲解。

原子哲学

jotai 为例,底层还是依赖了 React Provider (这里解释不包含 provider less mode)作为原子范围的隔离;

下面是 GC 原理,区别于 RecoilString key 作为键更稳定)

图片来自于:excalidraw.com/

动机:

  • 解决 provider 嵌套地狱 & 渲染地狱
  • 跨组件状态共享更加便捷;
  • 精准渲染订阅组件,避免 reRender

jotai 基础课程

Provider Less Mode

Provider 模式,有点类似于 React 18 中的 useSyncExternalStore

本质上还是借助 发布订阅模式代理模式 ,对订阅组件执行 forceUpdate 操作。

jotai 自己手撸了一个 源码,借助 WeakMap 实现的。

ts 复制代码
// source code from https://github.com/pmndrs/jotai/blob/main/src/vanilla/store.ts

export const createStore = () => {
  const atomStateMap = new WeakMap<AnyAtom, AtomState>()
  const mountedMap = new WeakMap<AnyAtom, Mounted>()
  const pendingMap = new Map<
    AnyAtom,
    AtomState /* prevAtomState */ | undefined
  >()
	// ....
}

那么最终的原子哲学是如何工作的呢?

图片来自于:blog.bitsrc.io/redux-free-...

浅看一下 useAtomValue 的源码:github.com/pmndrs/jota...

ts 复制代码
useEffect(() => {
    const unsub = store.sub(atom, () => {
      if (typeof delay === 'number') {
        // delay rerendering to wait a promise possibly to resolve
        setTimeout(rerender, delay)
        return
      }
      rerender()
    })
    rerender()
    return unsub
  }, [store, atom, delay])

本质就是发布订阅、观察者模式那一套。

Recoil -> Jotai

atom

  1. recoil
ts 复制代码
import { atom } from 'recoil';

const todosAtom = atom({
	key: 'TODO_STORE',
	default: {
		data: []
	}
})
  1. jotai
ts 复制代码
import { atom } from 'jotai';

const todosAtom = atom({
	data: []
})

useRecoilState -> useAtom

  1. recoil
ts 复制代码
import { useRecoilState } from 'recoil';

const [data, setData] = useRecoilState(todosAtom);
  1. jotai
ts 复制代码
import { useAtom } from 'jotai';

const [data, setData] = useRecoilState(todosAtom);

useSetRecoilState -> useSetAtom

  1. recoil
ts 复制代码
import { useSetRecoilState } from 'recoil';

const setData = useRecoilState(todosAtom);
  1. jotai
ts 复制代码
import { useSetAtom } from 'jotai';

const setData = useSetAtom(todosAtom);

useRecoilValue -> useAtomValue

  1. recoil
ts 复制代码
import { useRecoilValue } from 'recoil';

const data = useRecoilValue(todosAtom);
  1. jotai
ts 复制代码
import { useAtomValue } from 'jotai';

const data = useAtomValue(todosAtom);

useResetRecoilState -> useResetAtom

  1. recoil
ts 复制代码
import { useResetRecoilState } from 'recoil';

const reset = useResetRecoilState(todosAtom);
  1. jotai

如果你在 jotai 中定义的原子需要具备 resettable 能力,这里有两个注意事项:

  • 原子定义方式需要变化
  • API 导入变化

原子定义

ts 复制代码
import { atomWithReset, useResetAtom, RESET } from 'jotai/utils'

const todosAtom = atomWithReset({ data: [] })

API导入

ts 复制代码
import { useResetAtom } from 'jotai/utils'

const reset = useResetAtom(todosAtom)

useRecoilCallback -> useAtomCallback

  1. recoil
ts 复制代码
import { useRecoilCallback } from 'recoil';


const lazyLoad = useRecoilCallback(({ set, reset, snapshot }) => async () => {
	const data = snapshot.getPromise(todosAtom);
	// 打印 atom data
}, [])
  1. jotai
ts 复制代码
import { useCallback } from 'react';
import { useAtomCallback } from 'jotai/utils'

const lazyload = useAtomCallback(useCallback(async (get, set) => {
	const data = await get(todosAtom);
}, []));

到这里,从 recoiljotai 迁移的上篇就结束了,从 API 的迁移上看,核心的 API 迁移成本还是很小的,代码略微改造即可。

下篇预告:下篇则会着重介绍 异步原子 的迁移,各种实战 hack 写法,我们下期再见!

相关推荐
Ticnix2 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人2 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl2 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅2 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人2 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼2 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端
布列瑟农的星空2 小时前
前端都能看懂的Rust入门教程(三)——控制流语句
前端·后端·rust
Mr Xu_3 小时前
Vue 3 中计算属性的最佳实践:提升可读性、可维护性与性能
前端·javascript
jerrywus3 小时前
我写了个 Claude Code Skill,再也不用手动切图传 COS 了
前端·agent·claude
玖月晴空3 小时前
探索关于Spec 和Skills 的一些实战运用-Kiro篇
前端·aigc·代码规范