从 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 写法,我们下期再见!

相关推荐
哀木1 小时前
一个简单的套壳方案,就能让你的 Agent 少做重复初始化
前端
问心无愧05131 小时前
ctf show web入门27
前端
小村儿1 小时前
给 AI Agent 装上"长期记忆":Karpathy 的 LLM Wiki 思想,我做成了工具
前端·后端·ai编程
竹林8181 小时前
用ethers.js连接MetaMask实现Web3钱包登录:从踩坑到稳定运行的完整记录
前端·javascript
heyCHEEMS1 小时前
如何用 Recast 实现静态配置文件源码级读写
前端·node.js
心连欣1 小时前
从零开始,学习所有指令!
前端·javascript·vue.js
review445432 小时前
大模型和function calling分别是如何工作的
前端
东东同学2 小时前
耗时一个月,我把 Nuxt 首屏性能排障经验做成了一个 AI Skill
前端·agent
冴羽3 小时前
超越 Vibe Coding —— AI 辅助编程指南
前端·ai编程·vibecoding
梦想的颜色3 小时前
一天一个SKILL——前端最佳自动化测试 webapp-testing
前端·web app