如何使用自定义hooks对React组件进行重构
这篇文章主要介绍了如何使用自定义hooks对React组件进行重构的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇如何使用自定义hooks对React组件进行重构文章都会有所收获,下面我们一起来看看吧。
处理复杂性
我们应该要意识到,虽然函数组件被写在“一个函数”里,但是这个函数仍然可以像别的函数一样,可以由许多其他函数组成的。像useState
,useEffect
,抑或是别的hooks,子组件它们本身也是个函数。因此我们自然可以利用相同的思路来处理函数组件的复杂性问题:通过建立一个新函数,来把即符合公共模式又复杂的代码封装起来。
比较常见的处理复杂组件的方式是把它分解成多个子组件。但是这么做可能会让人觉得不自然或是很难准确的去描述这些子组件。这时候我们就可以借助梳理组件的钩子函数的逻辑来发现新的抽象点。
每当我们在组件内看到由useState
、useEffect
或是其他内置钩子函数组成的长长的列表时,我们就应该去考虑是否可以将它们提取到一个自定义hook中去。自定义hook函数是一种可以在其内部使用其他钩子函数的函数,并且创建一个自定义钩子函数也很简单。
如下所示的组件相当于一个看板,用一个列表展示一个用户仓库的数据(想像成和github类似的)。这个组件并不算是个复杂组件,但是它是展示如何应用自定义hook的一个不错的例子。
function Dashboard() { const [repos, setRepos] = useState<Repo[]>([]); const [isLoadingRepos, setIsLoadingRepos] = useState(true); const [repoError, setRepoError] = useState<string | null>(null); useEffect(() => { fetchRepos() .then((p) => setRepos(p)) .catch((err) => setRepoError(err)) .finally(() => setIsLoadingRepos(false)); }, []); return ( <div className="flex gap-2 mb-8"> {isLoadingRepos && <Spinner />} {repoError && <span>{repoError}</span>} {repos.map((r) => ( <RepoCard key={i.name} item={r} /> ))} </div> );}
我们要把钩子逻辑提取到一个自定义hook中,我们只需要把这些代码复制到一个以use
开头的函数中(在这里我们将其命名为useRepos
):
/** * 请求所有仓库用户列表的hook函数 */export function useRepos() { const [repos, setRepos] = useState<Repo[]>([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState<string | null>(null); useEffect(() => { fetchRepos() .then((p) => setRepos(p)) .catch((err) => setError(err)) .finally(() => setIsLoading(false)); }, []); return [repos, isLoading, error] as const;}
必须用use
开头的原因是linter
插件可以检测到你当前创建的是个钩子函数而不是普通函数,这样插件就可以检查你的钩子函数是否符合正确的自定义钩子的相关规则。
相比提炼之前,提炼后出现的新东西只有返回语句和as const
。这里的类型提示只是为了确保类型推断是正确的:一个包含3个元素的数组,类型分别是Repo[], boolean, string | null
。当然,你可以从钩子函数返回任何你希望返回的东西。
译者注:这里添加
as const
在ts类型推断的区别主要体现在数字元素的个数。不添加as const
,推断的类型为(string | boolean | Repo[] | null)[]
,添加后的类型推断为readonly [Repo[], boolean, string | null]
。
将自定义钩子useRepos
应用在我们的组件中,代码变成了:
function Dashboard() { const [repos, isLoadingRepos, repoError] = useRepos(); return ( <div className="flex gap-2 mb-8"> {isLoadingRepos && <Spinner />} {repoError && <span>{repoError}</span>} {repos.map((i) => ( <RepoCard key={i.name} item={i} /> ))} </div> );}
可以发现,我们现在在组件内部无法调用任何的setter
函数,即无法改变状态。在这个组件我们已经不需要包含修改状态的逻辑,这些逻辑都包含在了useRepos
钩子函数中。当然如果你确实需要它们,你也可以在钩子函数的返回语句中将其暴露出来。
这么做有什么好处呢?React的文档中有提到:
通过提取自定义钩子函数,可以实现组件逻辑的复用
我们可以简单的想象一下,如果这个应用中的别的组件也需要展示仓库中的用户列表,那么这个组件需要做的就只有导入useRepos
钩子函数。如果钩子更新了,可能使用某种形式的缓存,或者通过轮询或更复杂的方法进行持续更新,那么引用了这个钩子的所有组件都将受益。
当然,提取自定义钩子除了可以方便复用外,还有别的好处。在我们的例子中,所有的useState
和useEffect
都是为了实现同一个功能——就是获取库用户列表,我们把这个看作一个原子功能,那么在一个组件中,包含很多个这样的原子功能也是很常见的。如果我们把这些原子功能的代码都分别提取到不同的自定义钩子函数中,就更容易发现哪些状态在我们修改代码逻辑时要保持同步更新,不容易出现遗漏的情况。除此之外,这么做的好处还有:
越短小的函数越容易看懂
为原子功能命名的能力(如useRepo)
更自然的提供文档说明(每个自定义钩子函数的功能更加内聚单一,这种函数也很容易去写注释
关于“如何使用自定义hooks对React组件进行重构”这篇文章的内容就介绍到这里,感谢各位的阅读!相信大家对“如何使用自定义hooks对React组件进行重构”知识都有一定的了解,大家如果还想学习更多知识,欢迎关注恰卡编程网行业资讯频道。
相关文章
本站已关闭游客评论,请登录或者注册后再评论吧~