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(主要包括useState
、useEffect
和useContext
三个)。除此之外,它还允许你编写自己的高级 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}</>
}
所有这些加载和错误逻辑都可以拉入一个钩子中,使我们的界面更加整洁。
const MyTidyComponent = () => {
const {loading, result, error} = useAsync(doSomeAction)
if (loading) {
return <>loading...</>
}
if (error) {
return <>something broke</>
}
return <>{result}</>
}
这个useAsync
钩子负责管理加载、错误和结果状态,从而消除了实际组件中所有这些逻辑的需要。它还允许我们在整个应用程序中重复使用这些逻辑。这极大地简化了将数据加载到页面的过程。
另外,我们发现我们还希望能够稍后执行操作,而不是仅在组件创建时执行。这对于根据用户输入执行异步操作非常有用;像提交表单这样的操作可以使用相同的钩子,但会将false
值作为第二个参数传递。这表明他们不希望操作立即执行。
const { execute, loading, result, error } = useAsync(submitSomeForm, false)
<form onSubmit={execute}>
...
</form>
我们还发现,如果表单提交后离开组件,该钩子有时会导致内存泄漏(例如,表单提交后可能会跳转到下一页,但离开表单后再设置loading
到则会导致内存泄漏)。我们通过跟踪钩子是否已挂载到页面(通过 跟踪)来解决这个问题。只有当组件仍然存在时,我们才会更新状态。这样可以避免任何内存泄漏。false
useRef
我们的钩子的完整版本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 }
}
更新 LocalStorage 或 SessionStorage
在我们的一些产品中,我们会填充“购物篮”。这可以跟踪用户的操作。有时,我们希望即使用户离开我们的网站、刷新页面或关闭浏览器,这些信息也能保留下来。为了实现这一点,我们结合使用了localStorage和sessionStorage。
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}/>
}
我们实现这一目标的钩子如下所示:
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)
}
验证用户
我们遇到的一个非常常见的情况是,有一堆组件都关心用户是否登录。它们通常也关心通过诸如 或 之类的方法对用户login
采取logout
行动resetPassword
。
为了保持所有这些组件同步,我们只需要一个关于当前用户的信息源。我们可以用一个组件来包装整个应用程序,该组件管理状态,并将所有 props 传递到、或方法user
需要用到的地方。user
login
logout
resetPassword
但这很快就会变得混乱,因为许多组件并不真正关心被传递user
login
和logout
道具,即使它们自己不使用它们 - 只有它们的子组件才使用它们。
幸运的是,React 提供了context的概念,让我们可以解决这个问题。
我们可以创建一个 Auth 上下文,并使用钩子从中获取所需的任何信息。我们还可以将我们的 Auth API 调用嵌入到此上下文中。
使用起来如下:
// In our top level App.js
import { ProvideAuth } from 'hooks/useAuth'
export default () => {
return <ProvideAuth>
<RestOfApplication/>
...
</ProvideAuth>
}
// in a component that wants to use Auth
import useAuth from 'hooks/useAuth'
export default () => {
const { user, login, logout, resetPassword } = useAuth();
return <>
{user}
</>
}
这个钩子本身如下所示:
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
}
}
这样做还有一个额外的好处,那就是将所有身份验证逻辑都放在一起。要切换到不同的身份验证提供程序,我们只需修改这一个文件即可。
结论
React 提供了一些非常强大的抽象,用于创建组织有序、易于阅读的代码。本文,我们介绍了我认为最有用的三个 React Hook:useAsync
用于在组件创建或用户执行操作时执行异步操作;用于以与 相同的方式useStorage
使用localStorage
和;以及用于管理用户和身份验证。sessionStorage
useState
useAuth
这三个钩子提供了强大的抽象,让您以简单的方式构建 React 组件。
你还有其他觉得有用的自定义 React Hook 吗?觉得我遗漏了什么关键的吗?请告诉我。
还在寻找其他方法来保持代码的条理性吗?不妨看看我关于编写 IMMUTABLE 代码的文章。
喜欢这篇文章吗?想分享你的想法吗?觉得这篇文章有帮助吗?不同意我的观点吗?请在 Twitter 上留言告诉我。
文章来源:https://dev.to/dglsparsons/3-amazing-react-hooks-to-keep-your-code-organized-neatly-ghe