前言
在 React 16.8 之前,React 的 function 组件也称为无状态组件,因为 function 组件既不能访问 react 生命周期,也没有自己的状态。
自 React 16.8 起引入了 Hooks 概念,使得 function 组件可以通过 Hooks 模拟对应的 class 组件的生命周期,并且拥有了自己的状态。
Hook 是能在 function 组件里“钩入” React state 及生命周期等特性的 JavaScript 函数。
Hook 函数的使用规则:
- 可以在 React function 组件中调用 Hook,不能在 class 组件中调用 Hook(这使得你不使用 class 也能使用 React)。
- 可以在自定义的 Hook 函数中调用 Hook。
- 只能在函数最外层调用 Hook,不能在循环、条件判断或者子函数中调用 Hook。
React 官方提供了一个 linter 插件来强制执行 Hook 的规则。
一、react 内置的 Hook
1、useState
useState 只在初始化时执行一次,后面不再执行。
语法:
const [state变量, 设置该变量的方法] = useState(初始值);
通过在函数组件里调用 useState 来给组件添加一些内部 state:
- React 会在重复渲染时保留这个 state。
- useState 会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState,但是它不会把新的 state 和旧的 state 进行合并。
2、useEffect
useEffect 是 class 组件中的 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合 API,可以通过传参及其他逻辑,分别模拟这三个生命周期函数:
- useEffect 第二个参数是一个数组,如果数组为空时,则只执行一次(相当于componentDidMount)。
- 如果数组中有值时,则该值更新时,useEffect 中的函数才会执行(相当于componentDidUpdate)。
- 如果没有第二个参数,则每次render时,useEffect 中的函数都会执行。
React 保证了每次运行 effect 的同时,DOM 都已经更新完毕,也就是说 effect 中的获取的 state 是最新的,但是需要注意的是,effect 中返回的函数(其清除函数)中,获取到的 state 是更新前的。
传递给 useEffect 的函数在每次渲染中都会有所不同,这是刻意为之的。事实上这正是我们可以在 effect 中获取最新的值,而不用担心其过期的原因。每次我们重新渲染,都会生成新的 effect,替换掉之前的。某种意义上讲,effect 更像是渲染结果的一部分 —— 每个 effect 属于一次特定的渲染。
effect 的清除阶段(返回函数)在每次重新渲染时都会执行,而不是只在卸载组件的时候执行一次。它会在调用一个新的 effect 之前对前一个 effect 进行清理,从而避免了我们手动去处理一些逻辑 。
二、自定义 Hook
自定义 Hook 是一个函数,函数名以 “use” 开头,函数内部可以调用其他的 Hook。它更像是一种约定而不是功能。
自定义 Hook 是一种自然遵循 Hook 设计的约定,而并不是 React 的特性。
通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。
例如:
// 组件 FriendStatus 中
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
// 组件 FriendListItem 中
import React, { useState, useEffect } from 'react';
function FriendListItem(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
在组件 FriendStatus 和 FriendListItem 中,有公共的代码部分如下:
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
此时,就可以使用自定义 hook 将这公共的部分提取出来供这两个组件引用。该自定义 hook 的实现如下:
// 自定义 useFriendStatus hook
import { useState, useEffect } from 'react';
export function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
三、使用 hook 遇到的问题
1、react 函数组件使用了 hook 后闪屏
闪屏的原因分析与解决思路:
- 页面渲染,没有 loading 状态。(给页面添加 loading 状态,给会导致页面回流重绘的接口添加 loading 状态)
- 实时更新的数据没有更新。(使用 useState + useEffect 机制创建与监听实时更新的数据)