React简单教程-4.1-hook
前言
虽然我们简单感受了一下 useState
的用法,但我想你还是对 React 里的 hook 迷迷糊糊的。本文我们将明确下 React 的概念。
HOOK 🐕 前生今世
在我示例中,写的 React 组件都是函数的形式:
// 组件
function Component() {
return <div></div>;
}
使用这种写法的组件,叫做函数组件。然而,React 的组件一开始并不是这种写法的,而是如下的写法:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
这种使用 class 写法的组件,叫做 class 组件。
在 class 组件中,React 给我们定义了各种声明周期函数和 state,这些函数会在组件的各个阶段被调用,看起来就像这个样子:
class Welcome extends React.Component {
constructor(props) {
super(props);
this.state = { date: new Date() };
}
componentDidMount() {
// 组件加载完后做什么
this.setState({ date: new Date() });
}
componentWillUnmount() {
// 组件卸载后做什么
}
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
class 组件里如果有事件的话,还得处理 this
的问题:
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = { isToggleOn: true };
// 为了在回调中使用 `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState((prevState) => ({
isToggleOn: !prevState.isToggleOn,
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? "ON" : "OFF"}
</button>
);
}
}
这种 class 组件,编写几个功能就变得臃肿,且难以维护。
例如,组件常常在 componentDidMount 和 componentDidUpdate 中获取数据。但是,同一个 componentDidMount 中可能也包含很多其它的逻辑,如设置事件监听,而之后需在 componentWillUnmount 中清除。相互关联且需要对照修改的代码被进行了拆分,而完全不相关的代码却在同一个方法中组合在一起。如此很容易产生 bug,并且导致逻辑不一致。…… 我们还发现 class 是学习 React 的一大屏障
—— by React
为了应对 class 组件的问题,React 提倡使用函数组件的写法。相比起来,函数组件的写法简直优美。现在 React 仍然支持 class 组件的写法,但只是为了向前兼容,不推荐使用。
在使用函数组件的写法后,我们发现了,在 class 组件中的生命周期函数,在函数组件中无法使用,那怎么办呢?为了解决这个问题,React 16.8 新增了特性:hook。
其核心思想是:使用 hook(钩子),把你需要用的功能钩进来。
用法
如果我们要在函数组件里使用 class 组件里的 state,那么就用 hook,把 state 功能钩进来,就像我们上一章中使用的 useState
,可以让我们使用 class 的 state 功能:
import { useState } from "react";
function Component() {
// useState 钩子
const [value, setValue] = useState(0);
return <div></div>;
}
也可以使用多个 useState
hook:
import { useState } from "react";
function Component() {
// useState 钩子
const [value, setValue] = useState(0);
const [user, setUser] = useState({});
return <div></div>;
}
其他常用 hook
除了 useState
外,还有其他常用的钩子,多使用感受一下。
useEffect
class 组件中的声明周期函数,在函数组件中要怎么使用?就需要把 useEffect
钩进来,useEffect
是个函数,接收两个参数,第一个参数接收一个函数,之后传进来的这个函数将会在组件加载完成后执行。
import { useEffect } from "react";
function Component() {
// useEffect 钩子
useEffect(() => {
console.log("组件加载完毕!");
});
return <div></div>;
}
使用useEffect
这个方法就很适合用来发起请求数据。
useEffect
也能够实现组件卸载时候的生命周期,只需要给传进入的第一个函数参数返回一个函数即可:
import { useEffect } from "react";
function Component() {
// useEffect 钩子
useEffect(() => {
console.log("组件加载完毕!");
// 返回的函数将在组件卸载时执行
return () => {
console.log("组件卸载!");
};
});
return <div></div>;
}
这样,我们就可以在组件卸载时自动执行一些清理用的代码。
默认 useEffect
在组件每次加载后,还有组件重绘后,都会执行一遍,这样的话,如果我们使用 useEffect
来请求数据,就会在每次重绘界面的时候都请求一遍,这样是非常不适合的。
所幸,useEffect
还提供了第二个参数,可以通过传入一个数组,这样即使重绘了,只要数组内的数据没有变化,就不会触发 useEffect
。
import { useEffect } from "react";
function Component() {
let v = 0;
// useEffect 钩子
useEffect(() => {
// 请求数据
}, [v]);
return <div></div>;
}
上面的代码中,useEffect
的第二个参数我们传入了一个数组,其中的值是 v
,表示即使重绘了,只要这个值没有变化,就不会触发再次 useEffect
。如果我们只想在组件装载时执行一次,就算是重绘都不触发,那么只需要传入空数组即可。也很好理解,数组里没有任何数值,也就不会有数值变化,数值不会变化,在重绘时也就不会触发 useEffect
。
总之,useEffect
钩子会在组件装载时触发传入的函数,如果第二个参数没有传入,则会在每次装载和重绘时都触发。传入的函数如果有返回另一个函数,返回的函数就会在组件卸载时执行。
如果有多个功能需要在 useEffect
里使用,不推荐全部写在一个 useEffect
里,而是推荐每一个功能单独放在一个 useEffect
里。如下:
import { useEffect } from "react";
function Component() {
useEffect(() => {
// 请求数据
}, []);
useEffect(() => {
// 请求另外的数据
}, []);
return <div></div>;
}
这里的每个 useEffect
都会执行,且更容易维护,观感更佳。
结合起来使用
上面我们在介绍 useEffect
时说道可使用它来请求数据,如果说得完整一点的话,请求数据后还需要将数据呈现在界面上,就需要重绘界面,重绘界面需要调用到 setState,就需要在 useEffect
里调用到 setState —— 我们可以在一个 hook 里调用另一个 hook。
import { useEffect, useState } from "react";
function Component() {
const [data, setData] = useState(null);
useEffect(() => {
// 请求数据
const v = getData();
// 重绘
setData(v);
}, []);
return <div></div>;
}
你可以看到,hook 的使用跟函数的使用,没有什么区别,但是 hook 也有它的规定,hook 必须是在函数组件里使用,且按照约定,hook 的名字是 use 开头,推荐在函数组件的顶部使用。那么如果逻辑复杂的话,我们完全可以将复杂逻辑抽取出来,做成我们的自定义 hook。
自定义 hook
假设有一个需求,当组件加载后你需要往页面的 <head>
标签中加一个 <meta />
标签,你可能会在组件里这样写:
function Component() {
useEffect(() => {
/* 设置 <meta /> */
}, []);
return <div></div>;
}
我们假设这个逻辑非常复杂,代码长,且很多组件都要用,我们可以像提取方法一样提取 hook。
function useSetMeta() {
useEffect(() => {
/* 设置 <meta /> */
}, []);
}
我们提出了一个名为 useSetMeta
的 hook,接下来就和其他 hook 一样使用。别完了提取的 hook 要以 use 开头:
function Component() {
useSetMeta();
return <div></div>;
}
提取前和提取后的 hook 等价吗?完全等价,行为一致,我们没有对其行为做任何的改变,只是提取了代码到一个函数里。这样有助于更好得组织代码。
总结
在本文中,我们:
- 知道了 hook 的历史,hook 为何而来
- 了解
useState
和useEffect
的用法 - 知道了怎么做自定义 hook
- 知道了 hook 的规范:只能在组件里使用,以 use 开头
React 还有很多的 hook,可以使用,可自行查看,Hook API 索引。
参考资料
事件处理 by React
State & 生命周期 by React
Hook 简介 by React
React Hooks 入门教程 by 阮一峰的网络日志
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了