本篇文章会介绍Hook API,为了接下来对React底层的解析。
什么是Hook
Hook是一些可以让你在函数组件里钩入React state以及生命周期等特性的函数。
Hook 动机
Hook解决了下面这个问题:
在组件之间复用状态逻辑很难
这里会涉及自定义Hook,用来从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。
由于React抽象层组成的组件会形成“嵌套地狱”,所以React需要为共享状态的逻辑提供更好的原生途径。
复杂组件变得难以理解
这里会涉及Effect Hook,用来解决组件中的状态逻辑和副作用。每个生命周期常常包含一些不相关的逻辑。
为了解决这个问题,Hook将组件中相关联的部分拆分成更小的函数,而并非按照生命周期划分。
难以理解的Class
使用者不仅要区分函数组件和class组件的使用场景,还要对class不是很好压缩,并且会使热重载出现不稳定的情况进行处理。
为了解决这些问题,Hook使你在非class的情况下使用更多的React特性。
渐进策略
没有计划从React当中移除Class。Hook和现有的代码可以同时工作,可以渐进式的使用他们。
State Hook
下面是一个计数器例子:
import React, { useState } from 'react';
function Example() {
// 声明一个叫 “count” 的 state 变量。 const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
代码中的useState就是一个Hook,他会返回一对值:当前状态和让你更新这个状态的函数。
这个函数类似于class中的this.setState,但是不会将新的state和旧的state合并。
useState参数只有一个,代表初始state,这里的初始state是0。也可以给state初始值定义成对象,这需要看你的需求了。
Effect Hook
我们可能在React组件中执行过数据获取、订阅,或者手动修改过DOM。这些操作被统一称为"副作用"。
而 useEffect 这个Effect Hook,给函数增加了操作副作用的能力。跟class组件中的componentDidMount、componentDidUpdate、componentWillUnmount具有相同的用途,只不过被合并成一个API了。
例如,我们在React 更新了 DOM 之后,修改标题:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 相当于 componentDidMount 和 componentDidUpdate:
useEffect(() => {
// 使用浏览器的 API 更新页面标题
document.title = `You clicked ${count} times`;
});
return (
<div>
<div>{document.title}</div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
当调用useEffect的时候,就是通知React在完成对DOM的更改之后运行这个“副作用”函数。React会在每次渲染之后调用副作用函数,包括第一次渲染。
副作用函数可以通过返回一个函数,用来指定如何清除“副作用”。
通过使用Hook,我们可以将组件内相关的副作用组织在一起。而不是将他们拆分到不同的生命周期函数里。
自定义Hook
有时候我们想要在组件之间重用一些状态逻辑,目前有两种 高阶组件 和 render props。而自定义Hook可以让你在不增加组件的情况下达到我们的目的。
比如我们有一个组件来知道好友是否在线,他通过调用useState和useEffect的状态来知道好友在线的状态。
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
如果另一个组件里面要重用这部分逻辑,怎么办呢?
首先,把这部分逻辑抽取出来,我们把他抽取到我们自己定义的 useFriendStatus Hook中。
import React, { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
将friendID作为参数,返回好友是否在线的状态。
这样我们就可以在多个组件中使用它了,比如下面这两个组件:
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
虽然用的是同一个自定义Hook,但是每个组件的state是独立的。所以Hook复用的是逻辑本身,他不复用state状态。也就是说每次调用都会有一个独立的 state,所以你也可以在一个组件中多次调用。
什么是自定义Hook?
他更像是一种约定,而不是功能。如果函数名称用use开头,并且调用了其他Hook,我们可以说它是一个自定义的Hook。
Hook 使用规则
两个额外的规则:
- 只能在函数最外层调用,不要在循环、条件判断或者子函数中使用。
- 只能在React函数组件中使用。
后序会继续介绍Hook规则,这里先简单介绍这两个。
总结
本篇文章是对Hook诞生的理解,什么是 Hook? 为什么会存在 Hook? Hook 能做到什么?