这套题还不错,感兴趣的猿可以试一试:前端开发工程师
前言
在听到这么一个问题的时候,首先脑海中总体思路是这样子的:
- 认识自定义
Hook
的基本概念 - 能够使用自定义
Hook
进行复用逻辑的封装 - 了解自定义
Hook
的最佳实践与工作原理 - 进一步深入
Hook
底层原理思想 - 创造新的轮子思想,借鉴
Reack Hook
思想
什么是React自定义Hook
Hook
是 React 16.8
的新增特性。它可以让你在不编写 class
的情况下使用 state
以及其他的 React
特性。React
官方提供了常用的StateHook
和EffectHook
分别用以管理函数式组件状态和副作用。(React Hook官方介绍)。
除了官方提供的StateHook和EffectHook外,我们可以自己将常用的组件逻辑抽取到可重用的函数中,该函数须以React约定的形式来命名与使用,用以共享组件逻辑。
那么如何编写我们自己的自定义Hook
通过一个简单的例子来看一下 比如我们需要在拿到某个数据后设置页面标题,而且需要在各个页面里面使用
import React, { useState, useEffect } from 'react';
export default function App() {
// 正常我们这样子实现
const [title, setTitle] = useState('默认标题')
useEffect(() => {
document.title = title;
}, [title]);
const onTitleChange = (event) => {
setTitle(event.target.value);
}
return (<div>
<h2>{title}</h2>
<input type="text" onInput={onTitleChange}/>
</div>)
}
- 抽取共用逻辑,封装成自定义Hook
export function useSetPageTitle (initTitle) {
const [title, setTitle] = useState(initTitle || '默认标题');
useEffect(() => {
document.title = title;
}, [title]);
return [title, setTitle]
}
- 在其他组件中使用刚刚写的useSetPageTitle
import { useSetPageTitle } from '../App';
export default function Chirld() {
// 这里使用刚才写自定义Hook
const [title, setTitle] = useSetPageTitle();
return (<div>
<h2>{title}</h2>
<input type="text" onInput={onTitleChange}/>
</div>>)
}
这样子一个自定义的hook
就成型了,是不是瞬间感觉逼格提高了,原来我也可以写个这么高大上的自定义Hook
,哈哈哈。
聪明的你们一定发现了下面的特点
React约定自定义 Hook
必须以 use
开头
上面的例子中使用【useSetPageTitle】
。不遵循的话,由于无法判断某个函数是否包含对其内部 Hook
的调用,React 将无法自动检查你的 Hook
是否违反了 Hook
的规则。
自定义Hook
可自由搭配其他hook
使用
你可以自由使用其他内部Hook
和其他自定义Hook
.上面演示例子中使用useState
和useEffect
.
只在 React 的函数组件中最顶层使用 Hook
这样做是为了确保 Hook
在每一次渲染中都按照同样的顺序被调用。如果不是最顶层会导致状态或者执行方法出错进而导致BUG
。
- 反例
当state='A'
条件满足时执行调用Hook
顺序是正常,当后续渲染条件不满足时,则React
调用Hook
顺序出错,则会导致方法和状态逻辑执行出错。
import React, { useState, useEffect } from 'react';
import './App.css';
import { useSetPageTitle } from './hooks';
export default function App() {
const [state, setState] = useState('A');
// 反例
if (state === 'A') {
useEffect(() => {
console.log('只执行一次')
}, [state]);
}
useState({});
useEffect(() => {
// 获取数据
}, []);
useState({});
console.log(title)
return (
<div className="App">
<p> Hello React Hook! </p>
</div>
);
}
- 正例
import React, { useState, useEffect } from 'react';
export default function App() {
const [state, setState] = useState('A');
useEffect(() => {
if (state === 'A') {
console.log('执行相应的逻辑')
}
}, [state]);
useState({});
useEffect(() => {
// 获取数据
}, []);
useState({});
return (
<div className="App">
<p> Hello React Hook! </p>
</div>
);
}
为了避免出错我们可以使用ESLint插件来检测强制执行这些规则。 另外,亲测React官方提供的create-react-app @3.x与@4.x在eject后已经集成了插件
eslint-plugin-react-hooks
// 你的 ESLint 配置
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error", // 检查 Hook 的规则
"react-hooks/exhaustive-deps": "warn" // 检查 effect 的依赖
}
}
那么我们可以在哪些场景识别抽取自定义hook呢?
自定义 Hook
解决了以前在React
组件中无法灵活共享逻辑的问题。那么业务开发中可创建各种的自定义Hook
,公共辅助函数、状态复用、逻辑复用、效果复用、操作复用、生命周期模拟以及多种组合复用等只有想不到,相信很难有这么聪明的你们做不到的场景实现。
下面举几个简单栗子,抛砖引玉
- 例如使用
useMount
模拟生命周期componentDidMount
import React, { useState, useEffect } from 'react';
function useMount (fn) {
useEffect(() => {
fn();
}, []);
}
export default function App() {
useMount(() => {
// 你的逻辑
})
return (
<div className="App">
<p> Hello React Hook! </p>
</div>
);
}
- 利用
useEffect
返回函数是销毁才调用的机制来模拟unmount
function useUnmount (fn) {
useEffect(() => {
return () => {
fn();
}
}, []);
}
export default function App() {
useUnmount(() => {
console.log('销毁组件时输出')
})
return (
<div className="App">
<p> Hello React Hook! </p>
</div>
);
}
- 监听窗口变化
import React, { useState, useEffect } from 'react';
function useOnResize (fn) {
useEffect(() => {
window.addEventListener('resize',fn);
return () => {
window.removeEventListener('resize',fn)
}
}, []);
}
export default function App() {
useOnResize(() => {
console.log(document.body.clientWidth)
})
return (
<div className="App">
<p> Hello React Hook! </p>
</div>
);
}
这里介绍一些有趣的Hooks库供使用与参考
由蚂蚁 umi 团队、淘系 ice 团队以及阿里体育团队共同建设的 React Hooks 工具库ahooks
Set of a helpful hooks, for different specific to some primitives types state changing helpers.
可以使创建弹窗,提示,菜单变得非常容易,提供了创建DOM层次之外的元素的功能
用于发起Http请求的优秀Hook
发现更多优秀Hook库可在下面评论贴上,整一个优秀Hooks库的集合。