大家好,很高兴又见面了,我是"高级前端?进阶?",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!
React 组件通常具有多种 UI 状态,例如:加载中 (loading)、错误 (error) 和成功 (success)。 通常,开发者会使用多个 useState Hooks 来管理状态,但是这通常会导致代码难以阅读且极易出错,例如:
const MyComponent = () => {
const [loading, setLoading] = useState(false)
const [error, setError] = useState(false)
const [success, setSuccess] = useState(false)
// 使用多个 useState Hooks 管理状态
return (
<div>
{loading && !error && !success && <p>Loading...</p>}
{error && !loading && !success && <p>Error occurred</p>}
{success && !loading && !error && <p>Operation completed successfully</p>}
</div>
)
}
从代码层面看,状态彼此不同,比如:当 loading 为 true 时,error 和 success 应该为 false。 使用多个 useState Hooks 可能会导致意外行为,例如:同时将两个状态设置为 true。
此时开发者可以考虑使用 “有限状态机”(Finite State Machine,即 FSM) 模式,而 FSM 只允许有限数量的状态非常契合上面的场景。 在上面的 UI 示例中,单个 useState 可以更稳健地管理当前状态,并且出错的风险更低,如下所示:
import {useState} from 'react'
type State = 'loading' | 'error' | 'success'
const MyComponent = () => {
const [state, setState] = useState<State>('loading')
const handleClick = () => {
setState('loading')
// 模拟异步操作
setTimeout(() => {
setState('success')
}, 2000)
}
return (
<div>
{state === 'loading' && <p>Loading...</p>}
{state === 'error' && <p>Error occurred</p>}
{state === 'success' && <p>Operation completed successfully</p>}
<button onClick={handleClick}>Click me</button>
</div>
)
}
在某些情况下,例如:使用 Tanstack query(简化 Web 应用程序中的获取、缓存、同步和更新服务器状态)来获取数据时,useQuery 无需使用单独的 useState Hooks 来处理 loading, error 和 success 等多种状态:
import {
useQuery,
} from '@tanstack/react-query'
const MyComponent = () => {
const {data, isLoading, error} = useQuery(...)
if (isLoading) {
return <p>Loading...</p>
}
if (error) {
return <p>Error occurred</p>
}
return <p>Operation completed successfully {data}</p>
}
以上示例中,假如开发者还需要考虑另一种称为 “锁定” 的状态,其根据来自服务器的 403 状态指示用户是否已解锁该功能。通常开发者可能会使用 useState 和 useEffect 来管理状态,但是缺点就是会增加不必要的复杂性:
const MyComponent = () => {
const [locked, setLocked] = useState(false)
// 使用 useState Hooks 管理新的状态
const {data, isLoading, error} = useQuery(...)
useEffect(() => {
if (error && error.status === 403) {
setLocked(true)
}
}, [error])
if (locked) {
return <p>You are locked out</p>
}
}
然而,实际上更好的方法是直接从 error 状态中派生出 locked 状态,比如下面的例子:
const MyComponent = () => {
const {data, isLoading, error} = useQuery(...)
if (isLoading) {
return <p>Loading...</p>
}
const locked = error?.status === 403
if (locked) {
return <p>You are locked out</p>
}
}
此方法避免了使用 useState 和 useEffect 进行额外状态管理的需要。 因此,在编写 React 组件时,开发者还是要多考虑是否真的需要 useState 和 useEffect。正如上面的示例,很多 useState、useEffect 都能够被简化掉。
参考资料
本篇文章部分内容来自 Nico Prananta 发表的文章《You don't need useState in React》
https://www.nico.fyi/blog/you-dont-need-usestate-in-react
https://www.telerik.com/blogs/how-to-use-finite-state-machines-react
https://tanstack.com/query/latest/docs/framework/react/overview
https://github.com/tanstack/query
https://www.youtube.com/watch?v=h9hYnDe8DtI
https://www.youtube.com/watch?v=gTl5x_LQd_s