HOOKS
Hook 简介
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
阮一峰老师的React Hooks入门教程可能更加容易理解:
React 团队希望,组件不要变成复杂的容器,最好只是数据流的管道。开发者根据需要,组合管道即可。 组件的最佳写法应该是函数,而不是类。
React React Hooks 的设计目的,就是加强版函数组件,完全不使用"类",就能写出一个全功能的组件。
hook的含义
(摘自阮一峰老师的React Hooks入门教程)
Hook 这个单词的意思是"钩子"。
React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。 React Hooks 就是那些钩子。
你需要什么功能,就使用什么钩子。React 默认提供了一些常用钩子,你也可以封装自己的钩子。
所有的钩子都是为函数引入外部功能,所以 React 约定,钩子一律使用use前缀命名,便于识别。你要使用 xxx 功能,钩子就命名为 usexxx。
官方介绍什么是Hook?
Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。Hook 不能在 class 组件中使用 —— 这使得你不使用 class 也能使用 React。(我们不推荐把你已有的组件全部重写,但是你可以在新组件里开始使用 Hook。)
常用的hooks
State Hook
用一个例子来显示计数器按钮
import React, { useState } from 'react'
const LikeButton: React.FC = () => {
const [like, setlike] = useState(0)
return (
<button onClick={() => { setlike(like + 1) }}>
{like}👍
</button>
)
}
export default LikeButton
在这里,useState 就是一个 Hook,通过在函数组件里调用它来给组件添加一些内部 state。React 会在重复渲染时保留这个 state。 useState会返回一对值 当前状态和让你更新它的函数(上述例子中,当前状态为like 更新它的状态的变量为 setLike)你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState,但是它不会把新的state和旧的state进行合并。而是将新的state值进行覆盖。
useState的唯一参数就是state的初始值,上例点赞是从零开始的,所以初始值为0。这个初始的state的参数只有在第一次渲染时才会被用到。
- 声明多个 state 变量 :
你可以在一个组件中多次使用 State Hook:
function ExampleWithManyStates() {
// 声明多个 state 变量!
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
// ...
}
effect Hook
useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API。
例如,下面组件在React更新DOM后会设置一个页面标题:
import React, { useState, useEffect } from 'react'
const LikeButton: React.FC = () => {
const [like, setLike] = useState(0)
const [show, setShow] = useState(true)
useEffect(() => {
// 使用浏览器的 API 更新页面标题
document.title = `点击了${like}次`
},[like])
return (
<>
<button onClick={() => { setLike(like + 1) }}>
{like}👍
</button>
<button onClick={() => { setShow(!show)}}>
{show? 'ON' : 'OFF'}
</button>
</>
)
}
export default LikeButton
当你调用 useEffect 时,就是在告诉 React 在完成对 DOM 的更改后运行你的“副作用”函数。由于副作用函数是在组件内声明的,所以它们可以访问到组件的 props 和 state。默认情况下,React 会在每次渲染后调用副作用函数 —— 包括第一次渲染的时候。
副作用函数还可以通过返回一个函数来指定如何“清除”副作用。例如,在下面的组件中使用副作用函数来获取鼠标点击的位置,并通过取消事件监听器来进行清除操作:
import React, { useState, useEffect } from 'react'
const MouseTracker: React.FC = () => {
const [position, setPostion] = useState({ x: 0, y: 0 })
useEffect(() => {
const updateMouse = (e: MouseEvent) => {
setPostion({ x: e.clientX, y: e.clientY })
}
document.addEventListener('click', updateMouse)
return () => {
document.removeEventListener('click', updateMouse)
}
},[position])
return (
<>
<p> X: {position.x} Y: {position.y} </p>
</>
)
}
export default MouseTracker
在这个示例中,React 会在组件销毁时取消对鼠标click事件的监听,然后在后续渲染时重新执行副作用函数。
跟 useState 一样,你可以在组件中多次使用 useEffect :
import React, { useState, useEffect } from 'react'
const MouseTrackerWithLikeButton: React.FC = () => {
const [position, setPostion] = useState({ x: 0, y: 0 })
const [like, setlike] = useState(0)
useEffect(() => {
const updateMouse = (e: MouseEvent) => {
setPostion({ x: e.clientX, y: e.clientY })
}
document.addEventListener('click', updateMouse)
return () => {
document.removeEventListener('click', updateMouse)
}
},[position])
useEffect(() => {
document.title = `点击了${like}次`
},[like])
return (
<>
<p> X: {position.x} Y: {position.y} </p>
<button onClick={() => { setlike(like + 1) }}>
{like}👍
</button>
</>
)
}
export default MouseTrackerWithLikeButton
自定义 Hook
(以下同官方文档)
有时候我们会想要在组件之间重用一些状态逻辑。目前为止,有两种主流方案来解决这个问题:高阶组件和 render props。自定义Hook可以让你在不增加组件的情况下达到同样的目的。
我们现在有一个叫 FriendStatus组件,它通过调用 useState 和 useEffect 的 Hook 来订阅一个好友的在线状态。
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>
);
}
这两个组件的 state 是完全独立的。Hook 是一种复用状态逻辑的方式,它不复用 state 本身。事实上 Hook 的每次调用都有一个完全独立的 state —— 因此你可以在单个组件中多次调用同一个自定义 Hook。
自定义 Hook 更像是一种约定而不是功能。如果函数的名字以 “use” 开头并调用其他 Hook,我们就说这是一个自定义 Hook。 useSomething 的命名约定可以让我们的 linter 插件在使用 Hook 的代码中找到 bug。
Hook规则
- 只在最顶层使用 Hook
不要在循环,条件或嵌套函数中调用 Hook - 只在 React 函数中调用 Hook
不要在普通的 JavaScript 函数中调用 Hook。