Introduction
这是React16.8新增的附加物,让你在非class中使用state和其他功能。
非class组件,react中以前称呼为:stateless components,但新增了hooks功能后,比较喜欢称之为:function components函数组件。
hooks
就是为组件扩展功能的工具函数,一个具有特定调用方式的工具。如:useState,useEffects。让你能勾hook到react的功能。
什么时候使用?
如果之前的组件写的是stateless组件,此时发现这个组件需要state。
以前:用class组件重写
现在:使用hooks,如useState,而不用重写组件
useState
...
const [name, setName] = useState('anonymous');
// 如果要多个state,就调用多次useState
const [age, setAge] = useState(1);
...
Note(hooks的rules): 为什么取名用useState,而不用createState? 因为state只在组件第一次渲染的时候创建一次,所以用create描述并不太准确。在下一次更新渲染的时候,我们可以通过useState来获得正确的state值。【这就是为什么hooks的明明总是以use开头的☝️一个原因】
// 开发者沿用的class state的用法
...
const [info, setInfo] = useState({name: 'Lee', age: 12, sex: 1})
// 如何更新?
setInfo(info=>({...info, age: 13}));
// 或者
setInfo({...info, age: 13});
// useState中更新值采用replace的方式,this.setState采用merge方式??
...
如果你习惯使用自动merge的方式,你可以自定义useLegacyState。
**React官方建议:**将上面的state拆分为多个state变量,如:setAge, setName,合理的拆分会让相关的逻辑操作更简单。
useEffect
使能在function组件中执行side effects。
// 功能同class生命周期函数:componentDidMount componentDidUpdate,componentWillUnmount
useEffect(()=>{
document.title = `You clicked ${count} times`;
});
常见side effects(或者叫effects):
- data fetching
- setting up a subscription
- manually changing the DOM
另一种分类:
- 需要清理的side effects
- 网络请求
- 日志记录
- 手动修改dom
- 不需要cleanup的side effects
- 如果组件在mounting阶段订阅事件等,需要unMounting阶段取消订阅(如下:)
usage
在class组件中,render方法中不应该有直接触发side effect的代码,会导致死循环。我们通常会在DOM updated成功后执行effects。
以上就是为何我们将effects写在componentDidMount和componentDidUpdate的原因。
需要清理操作的effects
useEffect(()=>{
const doClick = () => {};
// 订阅、监听
document.addEventListener('click', doClick);
// 取消订阅、监听 cleanup只是为了阐述功能,可以用匿名函数
return function cleanup() {
document.removeEventListener('click', doClick);
}
})
👌TIPs
使用多个effects来划分问题! 在讲述hooks的出现motivation动机时候,讲到class的生命周期方法常会包裹一些不想关的逻辑,但是相关的逻辑通常会被拆分到多个周期函数里面。【逻辑拆分后在阅读、运维成为增加】
同state hooks一样,我们也可以基于功能使用多个effect hooks处理不同的逻辑。
通过跳过effects来优化组件性能! 在一些场景中,在每个render后执行effects的cleanup和apply操作可能会导致组件性能问题!!
在class中,我们在componentDidUpdate中通过比较prevState和prevProps来解决这个问题。
这是个常见的要求,在useEffect Hook API中已经内置了。如果指定的值没有被改变,那么React就会跳过应用这次效果。
// 只有count变化,这个effect才会re-run
useEffect(()=>{
document.title = `${count} times!`;
}, [count]);
// 确保:第二个参数的值在组件的scope中(props、state)且能随时间变化、且被effects使用了。
Note: 在React的将来的版本中,第二个参数可能会在构建时转换过程中自动添加。【期待】
如果只想对一个effect进行一次run和cleanup(mount、unmount),第二个参数传入空数组--[]。
❓疑问
为何在effect中return一个function? 这个effects hooks中一个可选的cleanup机制,每个effect可能会返回一个function用于在effects结束后做一些清理工作。作用:使得adding和removing代码紧密相连。
react会在何时准确的清理effect? 当组件unmounts的时候执行。但是,之前有讲,effects会在每次render后执行,而不仅仅只执行一次,所以也就会导致在下一次重新run effects的之前会cleans up上一个render中effects。
为何要在每个update后run effects? 在class中,你可能有疑问?为什么cleanup阶段发生在每次re-rendering(重渲染)后,而不仅仅在unmounting阶段发生。
这个设计能让我们新建的组件bug更少?场景如:如果没有update操作,props变化了:那么screen展现的只会是mounting过程中从props拿来的值,而不最新的值;在unmounting阶段cleanup的时候拿到了最新的props去做清理,就会导致a memory leak or crash;所以有些effects需要在每次componentDidUpdate中进行cleanup及重绑定工作,来解决这个react应用中常见的bug。
如果在function组件中,遇到如上的场景,使用Hooks,只需要一个简单的useEffect就能一次性解决上面三步操作---代码更简洁、明了、bug少。使用hooks不需要去关心didUpdate中的操作,这些会默认处理【清理上个render的effect,再运行下一个effect】
为什么Hooks必须放在top level? 不能在如下中调用hooks:loops, conditions, nested functions(嵌套函数)。
受React处理Hooks机制所限。如当有多个useState时候,React怎么知道哪个state对应的hooks被调用了?答案:React依赖与Hook的调用顺序。
通常情况不出现判断条件代码执行顺序不是变的。所有在调用hooks的时候不能将其放在条件、循环、嵌套函数代码中打乱了顺序。
使用官方推荐的 Linter 会在coding过程中提示。
定制自己的Hooks
将组件逻辑提取成可复用的函数并封装成hooks形式。