react指北:你真的会用hooks吗-上篇
前言
不知道大家有没有这种感觉,自从hooks出来之后,react的门槛越来越高了。在以前那个时代,写好react只需要关注生命周期和setState就可以,其他的数据模型就交给redux或者mobx。对于一般人而言,代码写的虽然不会多好,但是总算是不会出什么叉子,也能用。但是自从hooks出来之后,不同人写出来的代码可真是千差万别,有些甚至可以用惨不忍睹来形容。更有甚者,一行代码直接导致了整个组件崩溃。我甚至有时候都想,为了代码的生命安全,大家要不还是回到class component的时代算了。
但是显然,我们都不可能回到class component的时代了。所以,我觉得很有必要给大家再讲讲我理解的hooks。
hooks简介
hooks是react在16.8版本引入的新功能。hooks赋予了react全新的意义,解决了react长久以来未解决的两大问题:异步和状态复用。react官方在提到引入hooks的动机的时候也提到Hook使你在无需修改组件结构的情况下复用状态逻辑。
但是仅凭这些只言片语其实你很难真正理解hooks的伟大。很多初识hooks的人都会认为这仅仅只是另外一种意义上的class component。并且在实际的项目中,很多人也都是这样用的:一个大的function,里面包含了一堆的useState和useEffect,以及一个return,这就导致了组件的逻辑和渲染全都耦合在一起,一个逻辑复杂的组件代码量甚至有上千行之多,严重的影响了组件的可维护性。如果你,或者你的团队遇到了这样的问题,或许你需要认真的阅读接下里的内容。
react内置的hooks
react内置的hooks主要有以下几个:
- useState
- useEffect
- useContext
- useMemo
- useCallback
- useReducer
- useRef
- useImperativeHandle
- useLayoutEffect
- useDebugValue
上面这些api在你翻阅任何有关于react的hooks相关的资料的时候都能查询到,但是在实际项目中,我依然遇到很多人误用或者滥用这些hooks的行为。所以接下来我会用比较直白的语句给大家稍微再介绍下这些hooks。
useState
useState应该是大家用的最多的一个hooks。当你需要在fc中改变组件状态的时候,你就需要用到useState。语法我就不再赘述了,但是请记住一点,只有当你真正需要在组件内部改变某个状态的值的时候,你才需要用到useState。 如果你只是想要组件中记住某个变量的值,或许你需要的是useRef或者useMemo,这两个会更安全。
useEffect
useEffect除了useState以外另外一个频繁使用的hooks,用来监听组件的状态改变,相当于原先class component组件中的componentDidUpdate和componentDidMount这两个生命周期。稍微有点不一样的是,useEffect你可以在依赖数组里面设置你想要响应哪些状态的更改,而不是每次状态改变的时候都会被响应。所以相比于componentDidUpdate或componentDidMount,useEffect更容易组织你的逻辑,你可以将多个不同的状态响应写成多个useEffect,而不是在一个里面。例如:
const View = () => {
const [name, setName] = useState()
const [age, setAge] = useState()
useEffect(() => {
//age发生改变
}, [age])
useEffect(() => {
//name发生改变
}, [name])
return (
<div>
{name}
{age}
</div>
)
}
在上面代码中,我们不需要自己保存前后两次state的值来判断是哪个状态发生了改变。这点是之前class component所没有的优势。
另外还有一点,如果你在useEffect中返回了一个方法,这个方法会在该effect下一次被触发前触发,这就意味着每次更新或者卸载前,都会触发一次。实际工作中,我见过有人以为这个方法只会在组件卸载的时候触发,因此引发了一些奇怪的bug。
useContext
在介绍useContext之前,需要你对context有一些基本的了解。在react里面,如果你需要跨层级传递状态,你就需要用到context。而useContext可以让你在fc中更方便的使用context,仅此而已。
useMemo
useMemo允许你在组件中记住某个状态。例如,你想要在组件创建的时候生成一个自增的id,并且保存住:
const myId = useMemo(()=>++ID,[])
我见过很多useMemo和useState用混的情况,例如上面的代码有人会写成:
const [myId] = useState(++ID)
也没有什么问题,除了ID渲染的时候都会被无效自增一次外。但是更严重的是下面的写法:
const [myId,setMyId] = useState()
useEffect(()=>{
setMyId(++ID)
},[])
这就意味着你的组件会多一次无效的渲染。所以请记住我们在开始介绍useState的时候说的,只有你的状态改变必须触发组件更新的时候,你才需要useState。如果你的某个状态是依赖别的状态改变而改变,那么你应该使用useMemo,而不在useEffect中监听这个状态再setState。例如,请避免下面的写法:
const [year,setYear] = useState()
const [age,setAge] = useState()
useEffect(()=>{
setAge(new Date().getFullYear()-year)
},[year])
应该改成:
const [year,setYear] = useState()
const age = useMemo(()=>{
return new Date().getFullYear()-year
},[year])
useCallback
useCallback是useMemo的另外一种封装,相当于useMemo(()=>fn,deps)。
如果你的方法需要传递给其他组件作为属性使用,或者需要被作为依赖传入其他hooks中,那么你需要用useCallback封装一下。其他情况下使用,并不能给你带来性能的减少。
useReducer
useReducer是相对来讲比较复杂的一个hooks,需要你对reducer有一定的了解。如果你是redux的用户,理解起来应该问题不大。如果你没有用过redux,你可能需要先了解下useReducer和redux了。
看官方文档就行了,没啥太多讲解的必要。实际项目中,我比较少见到有同学使用,可能是本身对useReducer不熟悉,以及可能也不是完全需要一定要用useReducer解决的场景。
useRef
很多人会误以为useRef是createRef的另外一种形式(什么,你不知道createRef?那当我没说),实际上,他们几乎没有任何联系。要完全掌握useRef你需要把它和ref分离开来,先忘记ref。useRef在fc里面主要有两个作用:
- 每个useRef的值都是唯一的,能改变的只有它的current属性的值。
- 在每次渲染的时候,除非你手动修改useRef的current值都不会变,这意味着你可以用useRef来记住一些不会对组件渲染有任何影响的变量。
- 你可以用useRef来获得子组件的引用,以此来调用子组件中暴露出来的一些方法。
useRef最典型的用法我就不在这里赘述了,我们讲下另外一种比较典型的用法:
const isAb = useRef(Math.randon() > 0.5)
const abHandler = useCallback(() => {
if (isAb.current) {
alert('ab')
} else {
console.log('ab')
}
isAb.current = !isAb.current
}, [])
在上面这个例子中,我们就用了useRef来保存isAb的值,这样的话即使abHandler不需要重新创建也依然可以拿到isAb最新的值。
useImperativeHandle
useImperativeHandle比较简单,主要是让你可以在fc组件中暴露方法给父组件调用。你可以通过给组件传递useRef来获得useImperativeHandle暴露出来的方法。useImperativeHandle通常需要搭配forwardRef使用。如果你不走寻常路,不通过ref属性来传递,不用forwardRef也是ok的。
useLayoutEffect
useLayoutEffect和useEffect的调用方法一样,唯一的区别是,useLayoutEffect会保证在dom更新完成之后再被调用。如果你需要在effect中获取dom状态错误的话,你可以考虑使用useLayoutEffect。
尽可能的避免使用useLayoutEffect,因为它会阻塞dom渲染。
记住,你应该在仅当时尝试过useEffect失败之后再切换到useLayoutEffect。
useDebugValue
useDebugValue是用的比较少的一个hooks,可以让你在 React 开发者工具中显示自定义 hook 的标签。
我们来看一个官网的demo:
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// ...
// 在开发者工具中的这个 Hook 旁边显示标签
// e.g. "FriendStatus: Online" useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}
比较简单,就不多说了。
上篇小结
本来打算一篇搞定的,但是发现篇幅有点长,今天我们就先讲到这吧。关于hooks的介绍是结合了官方以及个人在长期项目中的一些理解,如果觉得和你的理解有不一样的地方,欢迎评论区交流。但是我相信,对大部分人而言,这肯定给你在日常的工作中带来一些帮助。
转载自:https://juejin.cn/post/7149932569331499039