优秀的编程知识分享平台

网站首页 > 技术文章 正文

在 React 中,你可能真的不需要 useState?

nanyue 2024-08-05 19:52:49 技术文章 9 ℃

家好,很高兴又见面了,我是"高级前端?进阶?",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!

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

最近发表
标签列表