性能
注意:本指南有改进的余地。 现在将其视为仅供参考。
Jotai & React 为我们提供了很多工具来管理应用程序生命周期中发生的重新渲染。 首先,请阅读 呈现和提交 之间的区别,因为这对 在进一步了解之前。
轻量的渲染
如 核心部分 所示,由于 React 18 的默认行为(但总体而言是良好实践),您必须确保您的组件函数是 幂等的。 它们将在渲染阶段被多次调用,即使是在挂载时。 所以我们需要不惜一切代价保持我们的渲染轻量!
繁重的计算
始终在 React 生命周期之外进行大量计算(例如在操作中)
不要:
// 每个 item 重计算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) => {// 捕获所有 friendsconst 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 应该渲染我们的组件。 如果您还没有看到 useMemo
和 useCallback
的用法,请在继续之前查看 React 官方文档以获取更多信息。
它们对于减少应用程序不流畅时不必要的渲染非常有用。
但 Jotai 还提供了一套工具来处理我们的原子应该在“何时”触发重新渲染。
- 开箱即用,Jotai 鼓励您将数据拆分为原子部分,因此每个原子都是单独存储的,并且只有在它们自己的值发生变化时才会触发重新渲染
selectAtom
允许您订阅大型对象的特定部分,并且仅在值更改时重新呈现focusAtom
与 selectAtom 相同,但为该部分创建一个新原子,提供一个 setter 以轻松更新该特定部分splitAtom
为动态列表完成 selectAtom/focus atom 的工作
看起来很简单,推理起来也很简单。 这就是目标,让我们保持简单以保持快速。
频繁或罕见的更新
问问自己,您的 Atom 通常会频繁更新还是很少更新。
让我们想象一个原子包含一个几乎每秒都在变化的对象,它可能不适合使用 focusAtom
“focus”这个对象的特定属性,因为无论如何它们都会同时重新渲染,所以最好 不增加开销,也不创建更多原子。
另一方面,如果您的对象具有很少更改的属性,最重要的是,这些属性的更改独立于其他属性,那么您可能希望使用 focusAtom
或selectAtom
来防止不必要的渲染。
“停止观察”模式
一个有趣的模式示例是使用 useMemo
仅读取一次原子值,以防止进一步渲染,即使原子在线发生变化也是如此。
让我们想象一个案例,您有一个切换列表。 让我们看看它的两种方法:
标准模式
我们创建了 3 个设置为 false 的 store
const togglesAtom = atom([false, false, false]);
然后,当用户点击一个开关时,我们更新它
const Item = ({ index, val }) => {const setToggles = useSetAtom(togglesAtom)const onPress = () => {setToggles(old => [...old, [index]: !val])}}const List = () => {const [toggles] = useAtom(togglesAtom)return toggles.map((val, index) => <Item id={index} val={val} />)}
使用这种方法,更新任何切换都会导致所有 <Item />
重新呈现。
Memoized 模式
现在让我们尝试 memoize 第一次渲染的值
const List = () => {const [toggles] = useMemo(() => useAtom(togglesAtom), []);return toggles.map((val, index) => <Item id={index} initialValue={val} />);};
但现在这意味着我们必须在每个 Item
中本地处理更改
const Item = ({ index, initialValue }) => {const setToggles = useSetAtom(togglesAtom)const [toggle, setToggle] = React.useMemo() => useAtom(atom(val)), [])const onPress = () => {setToggle(!toggle) // 更新本地setToggles(old => [...old, [index]: !val]) // 更新主要原子}}
现在如果你更新一个开关,它只会重新渲染相应的 <Item />