React hooks

380 阅读4分钟

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

另一种分类:

  1. 需要清理的side effects
  • 网络请求
  • 日志记录
  • 手动修改dom
  1. 不需要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形式。

参考

  1. React Document