从 Recoil 到 Jotai (上)

背景

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

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

现状

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

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

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


Recoil VS Jotai

参考:

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

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

Jotai

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

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


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

原子哲学

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

下面是 GC 原理,区别于 Recoil (String 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);
}, []));

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

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

相关推荐
计算机魔术师10 小时前
OpenAI 砍掉 100 亿融资目标,AI 资本市场的温度计变了
前端
jktantan11 小时前
(二)Vue 3 的 attrs 不是响应式:el-form 扩展属性同步的三种方案
前端·javascript·vue.js·element-plus
前端 贾公子11 小时前
第 5 章:Codex 从可运行程序到可交付工具
java·前端·数据库
用户3600555790011 小时前
Kestrel_VLLM - 26 · 发射空档三步曲:事件计时 → nsys → 判别实验
前端
echome88812 小时前
JavaScript async/await 错误处理:别让 Promise 静默失败
开发语言·前端·javascript·promise
PedroQue9912 小时前
v0.2.0 架构升级:class + reactive 重塑状态管理
前端·uni-app
鶴哥只手遮天12 小时前
Angular 22 源码级深度解析-第 02 章 编译链路总览:从模板字符串到 `ɵɵ` 指令
前端·开源·angular.js
尘世中一位迷途小书童12 小时前
别再堆万字 Prompt:用 Agent Skills 给团队写一个「前端 Review」Skill
前端·ai编程·cursor
aixingpan12 小时前
aixingpan.cn API开发文档:api_docs_bichart_common2接口指南
前端·php
super_Omni12 小时前
text/PDF/Word → 解析成纯文本 → 分词 → 词表匹配 → 加权打分 → 低于阈值
前端·chrome