我保证这个钩子会让你的 1000 多行异步代码焕然一新。
我可没有双关的意思! ;)
我从 Kent 的 Epic React Workshop 中学到的 hook代码useAsync()如下所示:
function useSafeDispatch(dispatch) {
const mounted = React.useRef(false)
React.useLayoutEffect(() => {
mounted.current = true
return () => (mounted.current = false)
}, [])
return React.useCallback(
(...args) => (mounted.current ? dispatch(...args) : void 0),
[dispatch],
)
}
const defaultInitialState = {status: 'idle', data: null, error: null}
function useAsync(initialState) {
const initialStateRef = React.useRef({
...defaultInitialState,
...initialState,
})
const [{status, data, error}, setState] = React.useReducer(
(s, a) => ({...s, ...a}),
initialStateRef.current,
)
const safeSetState = useSafeDispatch(setState)
const setData = React.useCallback(
data => safeSetState({data, status: 'resolved'}),
[safeSetState],
)
const setError = React.useCallback(
error => safeSetState({error, status: 'rejected'}),
[safeSetState],
)
const reset = React.useCallback(
() => safeSetState(initialStateRef.current),
[safeSetState],
)
const run = React.useCallback(
promise => {
if (!promise || !promise.then) {
throw new Error(
`The argument passed to useAsync().run must be a promise. Maybe a function that's passed isn't returning anything?`,
)
}
safeSetState({status: 'pending'})
return promise.then(
data => {
setData(data)
return data
},
error => {
setError(error)
return Promise.reject(error)
},
)
},
[safeSetState, setData, setError],
)
return {
isIdle: status === 'idle',
isLoading: status === 'pending',
isError: status === 'rejected',
isSuccess: status === 'resolved',
setData,
setError,
error,
status,
data,
run,
reset,
}
}
export {useAsync}
我们将使用钩子重构BookInfo下面的组件,通过精简多行代码使其更加优雅和健壮。💣
import * as React from 'react'
import {
fetchBook,
BookInfoFallback,
BookForm,
BookDataView,
ErrorFallback,
} from '../book'
function BookInfo({bookName}) {
const [status, setStatus] = React.useState('idle')
const [book, setBook] = React.useState(null)
const [error, setError] = React.useState(null)
React.useEffect(() => {
if (!bookName) {
return
}
setStatus('pending')
fetchBook(bookName).then(
book => {
setBook(book)
setStatus('resolved')
},
error => {
setError(error)
setStatus('rejected')
},
)
}, [bookName])
if (status === 'idle') {
return 'Submit a book'
} else if (status === 'pending') {
return <BookInfoFallback name={bookName} />
} else if (status === 'rejected') {
return <ErrorFallback error={error}/>
} else if (status === 'resolved') {
return <BookDataView book={book} />
}
throw new Error('This should be impossible')
}
function App() {
const [bookName, setBookName] = React.useState('')
function handleSubmit(newBookName) {
setBookName(newBookName)
}
return (
<div className="book-info-app">
<BookForm bookName={bookName} onSubmit={handleSubmit} />
<hr />
<div className="book-info">
<BookInfo bookName={bookName} />
</div>
</div>
)
}
export default App
我超级兴奋,我们开始吧!
但在继续之前,让我们先达成共识。
-
fetchBook从 API 获取数据,并返回 Promise,如果成功则返回书籍数据,如果被拒绝则返回错误信息。 -
BookInfoFallback这是你的加载器组件,它接受 bookName 作为参数来显示漂亮的加载效果。 -
BookForm这是一个简单的表单组件,用于接收用户数据。 -
BookDataView是一个外观精美的组件,用于向用户显示图书数据。 -
ErrorFallback显示美观的错误信息界面。
这些组件的实现超出了这篇博客的范围,但它们只是一些常规的东西。
我们的代码到底在干什么?
它从用户那里获取书名,并将其传递给组件,BookInfo该组件在钩子中处理获取书数据的操作,并根据不同的条件设置状态,它还处理成功获取、失败获取和加载时useEffect的渲染。BookDataViewErrorFallbackBookInfoFallback
好吧,我可能触发了什么。
“光说不练假把式,拿出代码来”的时刻。
import * as React from 'react'
import {
fetchBook,
BookInfoFallback,
BookForm,
BookDataView,
ErrorFallback,
} from '../book'
import useAsync from '../utils';
function BookInfo({bookName}) {
/////////////// Focus from here /////////////////
const {data: book, isIdle, isLoading, isError, error, run} = useAsync()
React.useEffect(() => {
if (!pokemonName) {
return
}
run(fetchPokemon(pokemonName))
}, [pokemonName, run])
if (isIdle) {
return 'Submit a book'
} else if (isLoading) {
return <BookInfoFallback name={bookName} />
} else if (isError) {
return <ErrorFallback error={error}/>
} else if (isSuccess) {
return <BookDataView book={book} />
}
//////////////// To here /////////////////
throw new Error('This should be impossible')
}
function App() {
const [bookName, setBookName] = React.useState('')
function handleSubmit(newBookName) {
setBookName(newBookName)
}
return (
<div className="book-info-app">
<BookForm bookName={bookName} onSubmit={handleSubmit} />
<hr />
<div className="book-info">
<BookInfo bookName={bookName} />
</div>
</div>
)
}
export default App
哇,现在是不是很棒?这不仅使我们的代码更易读,而且通过在组件卸载时不调用 dispatch 来增强组件的健壮性,此外,我们还对 fetch 方法进行了记忆化,以便在 bookName 没有改变时节省网络调用。
但是,Harsh,我们是不是为了完成一些很常见的事情而编写了更多代码?
是的,我们确实在这样做,但通过编写这个钩子,我们可以使用异步代码重构整个项目中的多个组件,这样可以节省大量时间,减少交付的代码量,并显著提高信心。
这是钩子的第一部分,useAsync()演示了它的使用案例。
接下来,我们将解耦钩子并从头开始构建它,解释每一行代码并学习一些巧妙的技巧。
我们还将在第三部分测试一下这个钩子,为什么不呢?
你是不是很期待第二部分的精彩内容?请在评论区留言,并将这篇文章分享给你的朋友们,让他们也兴奋起来!
简单介绍一下我自己,我希望通过创新和高质量的软件让世界变得更美好。
听起来是不是很熟悉?
没错,我是肯特·C·多兹的忠实粉丝,他激励了很多人。
这个钩子在他的 Epic React 工作坊中被广泛使用。快去看看他精彩的课程吧!
未来我也计划通过博客分享我的学习心得,让我们保持联系吧!
也别忘了查看本系列的其他博客文章!
文章来源:https://dev.to/harshkc/i-promise-this-hook-will-blow-your-1000-lines-of-async-code-2cao

