3 个出色的 REACT HOOKS,让你的代码井井有条

2025-05-25

3 个出色的 REACT HOOKS,让你的代码井井有条

大家好,我叫 Doug。我从事开发工作多年,目前担任 Shamaazi 的首席工程师。这段时间里,我编写了许多不同的 UI,并学习了许多构建 React 代码的方法。

本周我想分享我使用自定义 React Hooks 的经验,我发现它对于以最干净、最简单的方式制作网站最有用。

React Hooks

Hooks 于 2018 年首次亮相,并于 16.8 版本中首次引入 React。React官网上有一个很棒的指南介绍了 Hooks 。简而言之,Hooks 是一种为函数式 UI 组件编写副作用的方法。这使得你可以将 UI 的某些部分编写为 JavaScript 函数,同时仍然能够管理状态、调用 API、使用存储、验证用户身份等等。

React 提供了一些开箱即用的 hooks(主要包括useStateuseEffectuseContext三个)。除此之外,它还允许你编写自己的高级 hooks 来分离可复用逻辑。我将在这里探讨这些自定义 hooks。以下是我在 Shamaazi 生产的一系列产品中发现最有用的三个 hooks。

执行异步操作

大多数网站都必须执行某种形式的异步操作,无论是加载数据以在页面上显示,还是根据用户的输入和操作提交数据。跟踪这些异步操作的状态很有帮助:当前是否正在加载?是否已返回结果?是否发生了错误?

我们发现很多组件开始共享类似的代码,无论是在初始加载时获取数据,还是在提交数据。如下所示:

const MyComponent = () => {
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState(null)
  const [result, setResult] = useState(null)

  useEffect(() => {
    const loadData = async () => {
      setResult(null)
      setError(null)
      setLoading(true)
      try {
        const result = await doSomeAction();
        setResult(result)
      } catch (e) {
        setError(e)
      } finally {
        setLoading(false)
      }

    loadData()
  }, [])

  if (loading) {
    return <>loading...</>
  }

  if (error) {
    return <>something broke</>
  }

  return <>{result}</>
}
Enter fullscreen mode Exit fullscreen mode

所有这些加载和错误逻辑都可以拉入一个钩子中,使我们的界面更加整洁。

const MyTidyComponent = () => {
  const {loading, result, error} = useAsync(doSomeAction)

  if (loading) {
    return <>loading...</>
  }

  if (error) {
    return <>something broke</>
  }

  return <>{result}</>
}
Enter fullscreen mode Exit fullscreen mode

这个useAsync钩子负责管理加载、错误和结果状态,从而消除了实际组件中所有这些逻辑的需要。它还允许我们在整个应用程序中重复使用这些逻辑。这极大地简化了将数据加载到页面的过程。

另外,我们发现我们还希望能够稍后执行操作,而不是仅在组件创建时执行。这对于根据用户输入执行异步操作非常有用;像提交表单这样的操作可以使用相同的钩子,但会将false值作为第二个参数传递。这表明他们不希望操作立即执行。

const { execute, loading, result, error } = useAsync(submitSomeForm, false)

<form onSubmit={execute}>
  ...
</form>
Enter fullscreen mode Exit fullscreen mode

我们还发现,如果表单提交后离开组件,该钩子有时会导致内存泄漏(例如,表单提交后可能会跳转到下一页,但离开表单后再设置loading到则会导致内存泄漏)。我们通过跟踪钩子是否已挂载到页面(通过 跟踪)来​​解决这个问题。只有当组件仍然存在时,我们才会更新状态。这样可以避免任何内存泄漏。falseuseRef

我们的钩子的完整版本useAsync在这里:

import { useEffect, useState, useCallback, useRef } from 'react'

export default (asyncFunction, immediate = true) => {
  const [loading, setLoading] = useState(false)
  const [result, setResult] = useState(null)
  const [error, setError] = useState(null)

  // Track a reference to whether the useAsync is actually on a mounted component.
  // useEffect below returns a cleanup that sets this to false. Before setting
  // any state, we check if the cleanup has run. If it has, don't update the state.
  const mounted = useRef(true)

  useEffect(() => {
    return () => {
      mounted.current = false
    }
  }, [])

  const execute = useCallback(async (...args) => {
    setLoading(true)
    setResult(null)
    setError(null)
    try {
      const r = await asyncFunction(...args)
      if (mounted.current) {
        setResult(r)
      }
      return r
    } catch (e) {
      if (mounted.current) {
        setError(e)
      }
    } finally {
      if (mounted.current) {
        setLoading(false)
      }
    }
  }, [asyncFunction])

  useEffect(() => {
    if (immediate) {
      execute()
    }
  }, [execute, immediate])

  return { execute, loading, result, error }
}
Enter fullscreen mode Exit fullscreen mode

更新 LocalStorage 或 SessionStorage

在我们的一些产品中,我们会填充“购物篮”。这可以跟踪用户的操作。有时,我们希望即使用户离开我们的网站、刷新页面或关闭浏览器,这些信息也能保留下来。为了实现这一点,我们结合使用了localStoragesessionStorage。

React 本身不提供任何用于在localStorage或中存储数据的钩子sessionStorage,但我们希望获得一致的使用体验useState。实际上,它的使用应该不会localStorage比正常使用状态更难。

例如,我们可能想要使用它localStorage来跟踪用户的输入。

const storageComponent = () => {
  const [value, setValue] = useLocalStorage('storage_key', 'default_value')

  return <input value={value} onChange={e => setValue(e.target.value}/>
}
Enter fullscreen mode Exit fullscreen mode

我们实现这一目标的钩子如下所示:

const useStorage = (key, initialValue, storage) => {
  // Pass initial state function to useState so logic is only executed once
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = storage.getItem(key)
      return item ? JSON.parse(item) : initialValue
    } catch (error) {
      console.error(error)
      return initialValue
    }
  })

  useEffect(() => {
    try {
      // Update storage every time the value is changed
      storage.setItem(key, JSON.stringify(storedValue))
    } catch (e) {
      console.error(e)
    }
  }, [storedValue, storage, key])

  return [storedValue, setStoredValue]
}

export const useLocalStorage = (key, initialValue) => {
  return useStorage(key, initialValue, window.localStorage)
}

export const useSessionStorage = (key, initialValue) => {
  return useStorage(key, initialValue, window.sessionStorage)
}
Enter fullscreen mode Exit fullscreen mode

验证用户

我们遇到的一个非常常见的情况是,有一堆组件都关心用户是否登录。它们通常也关心通过诸如 或 之类的方法对用户login采取logout行动resetPassword

为了保持所有这些组件同步,我们只需要一个关于当前用户的信息源。我们可以用一个组件来包装整个应用程序,该组件管理状态,并将所有 props 传递到或方法user需要用到的地方userloginlogoutresetPassword

但这很快就会变得混乱,因为许多组件并不真正关心被传递user loginlogout道具,即使它们自己不使用它们 - 只有它们的子组件才使用它们。

幸运的是,React 提供了context的概念,让我们可以解决这个问题。

我们可以创建一个 Auth 上下文,并使用钩子从中获取所需的任何信息。我​​们还可以将我们的 Auth API 调用嵌入到此上下文中。

使用起来如下:

// In our top level App.js
import { ProvideAuth } from 'hooks/useAuth'

export default () => {
  return <ProvideAuth>
    <RestOfApplication/>
    ...
  </ProvideAuth>
}
Enter fullscreen mode Exit fullscreen mode
// in a component that wants to use Auth
import useAuth from 'hooks/useAuth'

export default () => {
  const { user, login, logout, resetPassword } = useAuth();

  return <>
    {user}
  </>
}
Enter fullscreen mode Exit fullscreen mode

这个钩子本身如下所示:

import React, { useCallback, useState, useEffect, useContext, createContext } from 'react'

const authContext = createContext()

// Hook for child components to get the auth object and re-render when it changes.
export default () => {
  return useContext(authContext)
}

// Provider component that wraps components and makes useAuth() available
export function ProvideAuth({ children }) {
  const auth = useAuthProvider()
  return <authContext.Provider value={auth}>{children}</authContext.Provider>
}

// Provide Auth hook that creates auth object and handles state
function useAuthProvider() {
  const [user, setUser] = useState(null)

  // Get the logged in user when created
  useEffect(() => {
    const user = getLoggedInUser()
    setUser(user)
  }, [])

  const login = async (...) => {
    const user = ...
    setUser(user)
  }

  const logout = async () => {
    ...
    setUser(null)
  }

  const resetPassword = async () => {
    ...
  }

  return {
    resetPassword
    login,
    logout,
    user
  }
}
Enter fullscreen mode Exit fullscreen mode

这样做还有一个额外的好处,那就是将所有身份验证逻辑都放在一起。要切换到不同的身份验证提供程序,我们只需修改这一个文件即可。

结论

React 提供了一些非常强大的抽象,用于创建组织有序、易于阅读的代码。本文,我们介绍了我认为最有用的三个 React Hook:useAsync用于在组件创建或用户执行操作时执行异步操作;用于以与 相同的方式useStorage使用localStorage;以及用于管理用户和身份验证。sessionStorageuseStateuseAuth

这三个钩子提供了强大的抽象,让您以简单的方式构建 React 组件。


你还有其他觉得有用的自定义 React Hook 吗?觉得我遗漏了什么关键的吗?请告诉我。


还在寻找其他方法来保持代码的条理性吗?不妨看看我关于编写 IMMUTABLE 代码的文章


喜欢这篇文章吗?想分享你的想法吗?觉得这篇文章有帮助吗?不同意我的观点吗?请在 Twitter 上留言告诉我

文章来源:https://dev.to/dglsparsons/3-amazing-react-hooks-to-keep-your-code-organized-neatly-ghe
PREV
如何编写 IMMUTABLE 代码,避免再次陷入调试困境
NEXT
2019-2020 年你可能不知道的 JavaScript 功能私有类字段👇 String.matchAll()👇 数字分隔符👇 BigInt's👇 带有 BigInt 的语言环境字符串👇 GlobalThis 关键字👇 Promise.allSettled()👇 动态导入👇 稳定排序 —(现在结果一致可靠)👇