状态管理:如何区分坏布尔值和好布尔值
观察
TL;DR:坏的布尔值代表状态。好的布尔值源自状态。
在应用中管理状态时,很容易被错误的布尔值所困扰。错误的布尔值如下所示:
let isLoading = true;
let isComplete = false;
let hasErrored = false;
表面上看,这段代码不错。看起来你用合适的布尔值名称表示了三个独立的状态。在你为状态绘制的“模型”中,任何时刻只有一个状态可以为真。
在获取请求中,您可以像这样建模状态:
const makeFetch = async () => {
isLoading = true;
try {
await fetch('/users');
isComplete = true;
} catch (e) {
hasErrored = true;
}
isLoading = false;
};
再次,这看起来很棒。我们在处理异步请求时协调了布尔值。
但是这里有个 bug。如果我们获取数据成功了,然后我们再次获取数据,会发生什么?最终会得到:
let isLoading = true;
let isComplete = true;
let hasErrored = false;
隐式状态
你在创建初始模型时可能没有考虑到这一点。你可能有一些前端组件正在检查isComplete === true
或isLoading === true
。最终你可能会得到一个加载旋转器,同时显示之前的数据。
这怎么可能呢?嗯,你已经创建了一些隐式状态。假设你考虑了三种你真正想要处理的状态:
loading
:加载数据complete
:显示数据errored
:如果数据没有出现,则出错
嗯,你实际上允许了8 个状态!第一个布尔值是 2,第二个布尔值乘以 2,第三个布尔值也乘以 2。
这就是所谓的布尔爆炸——我从Kyle Shevlin 的 egghead 课程中了解到了这一点。
使状态明确
如何解决这个问题?我们需要一个包含 3 个可能值的系统,而不是 8 个。在 Typescript 中,我们可以使用枚举来实现这一点。
type Status = 'loading' | 'complete' | 'errored';
let status: Status = 'loading';
我们将通过如下方式实现这一点:
const makeFetch = async () => {
status = 'loading';
try {
await fetch('/users');
status = 'complete';
} catch (e) {
status = 'errored';
}
};
现在不可能同时处于“加载中”和“完成”状态了——我们已经修复了这个问题。我们把错误的布尔值变成了正确的枚举值。
制作好的布尔值
但并非所有布尔值都是不好的。许多流行的库,例如react-query
、apollo
和 ,都urql
在其状态中使用布尔值。以下是示例实现:
const [result] = useQuery();
if (result.isLoading) {
return <div>Loading...</div>;
}
这些布尔值之所以是好的布尔值,是因为它们的底层机制基于枚举。不好的布尔值代表状态。好的布尔值源自状态:
let status: Status = 'loading';
// Derived from the status above
let isLoading = status === 'loading';
您可以安全地使用它isLoading
来显示您的加载微调器,并很高兴地知道您已经删除了所有不可能的状态。
附录:JavaScript 中的枚举
评论里有几个人问如何在 JavaScript 中表示状态枚举。虽然上面的代码不需要类型定义也能运行,但你也可以将枚举表示为对象类型。
const statusEnum = {
loading: 'loading',
complete: 'complete',
errored: 'errored',
};
let status = statusEnum.loading;
const makeFetch = async () => {
status = statusEnum.loading;
try {
await fetch('/users');
status = statusEnum.complete;
} catch (e) {
status = statusEnum.errored;
}
};