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

相关推荐
熊的猫42 分钟前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js
瑶琴AI前端1 小时前
uniapp组件实现省市区三级联动选择
java·前端·uni-app
会发光的猪。1 小时前
如何在vscode中安装git详细新手教程
前端·ide·git·vscode
别拿曾经看以后~2 小时前
【el-form】记一例好用的el-input输入框回车调接口和el-button按钮防重点击
javascript·vue.js·elementui
我要洋人死2 小时前
导航栏及下拉菜单的实现
前端·css·css3
川石课堂软件测试2 小时前
性能测试|docker容器下搭建JMeter+Grafana+Influxdb监控可视化平台
运维·javascript·深度学习·jmeter·docker·容器·grafana
科技探秘人3 小时前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人3 小时前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR3 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香3 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel