JotaiJotai

Jotai
React 原始而灵活的状态管理

性能

注意:这篇指南还有改进的空间。目前请将其视为参考信息。

Jotai 和 React 为我们提供了相当多的工具来管理应用生命周期中发生的重新渲染。 首先,请阅读关于渲染与提交的区别,因为在进一步了解之前,理解这个非常重要。

低成本渲染

核心部分所述,由于 React 18 的默认行为(但总体上是好的实践),你必须确保你的组件函数是幂等的。 它们将在渲染阶段被多次调用,甚至在挂载时。所以我们需要尽可能地降低我们的渲染成本!

重计算

始终在 React 生命周期之外进行重计算(例如在操作中)

不推荐的做法:

// 每个项目的重计算
const selector = (s) => s.filter(heavyComputation)
const Profile = () => {
const [computed] = useAtom(selectAtom(friendsAtom, selector))
}

推荐的做法:

const friendsAtom = atom([])
const fetchFriendsAtom = atom(null, async (get, set, payload) => {
// 获取所有朋友
const res = await fetch('https://...')
// 只进行一次重计算
const computed = res.filter(heavyComputation)
set(friendsAtom, computed)
})
// 在组件中使用
const Profile = () => {
const [friends] = useAtom(friendsAtom)
}

小组件

观察到的原子应该只重新渲染你的应用程序需要更新的小部分。React 需要进行的比较越少,你的渲染时间就越短。

不推荐的做法:

const Profile = () => {
const [name] = useAtom(nameAtom)
const [ageAtom] = useAtom(ageAtom)
return (
<>
<div>{name}</div>
<div>{age}</div>
</>
)
}

推荐的做法:

const NameComponent = () => {
const [name] = useAtom(nameAtom)
return <div>{name}</div>
}
const AgeComponent = () => {
const [age] = useAtom(ageAtom)
return <div>{age}</div>
}
const Profile = () => {
return (
<>
<NameComponent />
<AgeComponent />
</>
)
}

按需渲染

通常,主要的性能开销将来自于重新渲染你的应用程序不需要的部分,或者比应该的多得多。

我们有一些工具来处理 React 应该何时渲染我们的组件。如果你还没有看到 useMemouseCallback 的使用,请在进一步了解之前查看官方的 React 文档以获取更多信息。 它们对于减少你的应用程序不流畅的地方的不必要渲染非常有用。

但是 Jotai 也提供了一套工具来处理我们的原子应该何时触发重新渲染。

  • 开箱即用,Jotai 鼓励你将数据分解成原子部分,因此每个原子都被单独存储,并且只有在它们自己的值改变时才会触发重新渲染
  • selectAtom 允许你订阅大对象的特定部分,并且只有在值改变时才重新渲染
  • focusAtom 与 selectAtom 相同,但是为部分创建一个新的原子,提供一个 setter 轻松更新该特定部分
  • splitAtom 为动态列表完成 selectAtom/focusAtom 的工作

虽然这看起来很简单,但是它很容易理解。这就是目标,让它保持简单以保持快速。

频繁或稀有的更新

问问自己你的原子通常是否会频繁更新或更少。 让我们想象一个原子包含一个几乎每秒都在变化的对象,使用 focusAtom 来关注这个对象的特定属性可能不是最好的选择,因为无论如何它们都会在同一时间重新渲染,所以最好不要增加开销,也不要创建更多的原子。

另一方面,如果你的对象有很少改变的属性,最重要的是,这些属性独立于其他属性改变,那么你可能想要使用 focusAtomselectAtom 来防止不必要的渲染。

"停止观察"模式

一个可能有趣的模式示例是使用 useMemo 在第一次渲染时只读取一次原子值,以防止即使原子在后面改变也进一步渲染。

让我们想象一个情况,你有一个切换列表。让我们看看两种方法:

标准模式

我们创建一个设置为 false 的 3 个切换的存储

const togglesAtom = atom([false, false, false])

然后,当用户点击一个切换时,我们更新它

const Item = ({ index, val }) => {
const setToggles = useSetAtom(togglesAtom)
const onPress = () => {
// setToggles(old => [...old, [index]: !val])
setToggles(old => {
const newArray = [...old];
newArray[index] = !newArray[index];
return newArray;
})
}
}
const List = () => {
const [toggles] = useAtom(togglesAtom)
return toggles.map((val, index) => <Item index={index} val={val} />)
}

使用这种方法,更新任何切换都会导致所有的 <Item /> 重新渲染。

Memoized 模式

现在让我们尝试在第一次渲染时记住值

const togglesAtom = atom([atom(false), atom(false), atom(false)])
const List = () => {
const [toggles] = useAtom(togglesAtom)
return toggles.map((val, index) => <Item index={index} val={val} />)
}

但现在这意味着我们必须在每个 Item 中处理变化

const Item = ({ index, val }) => {
const [toggle, setToggle] = useAtom(val)
const onPress = () => {
setToggle(!toggle) // 本地更新
}
}

现在如果你更新一个切换,它只会重新渲染对应的 <Item />