JotaiJotai

状態
Primitive and flexible state management for React

性能

注意:本指南有改进的余地。 现在将其视为仅供参考。

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) => {
// 捕获所有 friends
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/focus atom 的工作

看起来很简单,推理起来也很简单。 这就是目标,让我们保持简单以保持快速。

频繁或罕见的更新

问问自己,您的 Atom 通常会频繁更新还是很少更新。 让我们想象一个原子包含一个几乎每秒都在变化的对象,它可能不适合使用 focusAtom “focus”这个对象的特定属性,因为无论如何它们都会同时重新渲染,所以最好 不增加开销,也不创建更多原子。

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

“停止观察”模式

一个有趣的模式示例是使用 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 />