前言

虽然我们简单感受了一下 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 等价吗?完全等价,行为一致,我们没有对其行为做任何的改变,只是提取了代码到一个函数里。这样有助于更好得组织代码。

总结

在本文中,我们:

  1. 知道了 hook 的历史,hook 为何而来
  2. 了解 useStateuseEffect 的用法
  3. 知道了怎么做自定义 hook
  4. 知道了 hook 的规范:只能在组件里使用,以 use 开头

React 还有很多的 hook,可以使用,可自行查看,Hook API 索引

参考资料

事件处理 by React

State & 生命周期 by React

Hook 简介 by React

React Hooks 入门教程 by 阮一峰的网络日志

React简单教程-4.1-hook的更多相关文章

  1. React简单教程-4-事件和hook

    前言 在上一章 React 简单教程-3-样式 中我们建立了一个子组件,并稍微美化了一下.在另一篇文章 React 简单教程-3.1-样式之使用 tailwindcss 章我们使用了 tailwind ...

  2. React简单教程-6-单元测试

    前言 我想大部分人的前端测试,都是运行项目,直接在浏览器上操作,看看功能正不正常.虽然明明有测试库可以使用,但是因为"要快"的原因,让好好做测试变成了一件影响效率的事. 因为这种无 ...

  3. React简单教程-3-样式

    前言 在上一章 React 简单教程-2-ts 和组件参数 中我们新建的子组件 Displayer 没有样式,显得平平无奇,这一篇我们将给他美化一下. CSS 文件 一般的做法,是在你的组件级目录下新 ...

  4. React简单教程-2-ts和组件参数

    前言 在上一章:React 简单教程-1-组件 我们知道了 React 的组件是什么,长什么样,用 js 和 HTML 小小体验了一下组件.在这一章,我们将使用 typescript(简称 ts) 来 ...

  5. React简单教程-1-组件

    前言 React,Facebook开发的前端框架.当时Facebook对市面上的前端框架都不满意,于是自己捣鼓出了React,使用后觉得特别好用,于是就在2013年开源了. 我也用React开发了一个 ...

  6. React简单教程-5-使用mock

    前言 一个前后端分离的项目,前端人员需要对接后端的接口.如果在后端的接口没有开发好,或者没有测试版可以对接的情况下,前端人员也不能坐等后端接口写好后再开始开发. 一个项目的,理想情况下接口的规范应该是 ...

  7. React简单教程-3.1-样式之使用 tailwindcss

    前言 本文是作为一个额外内容,主要介绍 tailwindcss 的用法 tailwindcss 是一个功能类优先的 CSS 框架,我在以前的文章里有描述为什么使用功能类优先:为什么我在 css 里使用 ...

  8. react+redux教程(五)异步、单一state树结构、componentWillReceiveProps

    今天,我们要讲解的是异步.单一state树结构.componentWillReceiveProps这三个知识点. 例子 这个例子是官方的例子,主要是从Reddit中请求新闻列表来显示,可以切换reac ...

  9. react+redux教程(四)undo、devtools、router

    上节课,我们介绍了一些es6的新语法:react+redux教程(三)reduce().filter().map().some().every()....展开属性 今天我们通过解读redux-undo ...

随机推荐

  1. 【Android开发】【数据库】Realm For Android

    目录 1.Realm简介 2.环境配置 3.初始化Realm 4.创建实体 5.增 6.删 7.改 8.查 9.异步操作 10.数据库数据更新监听 11.json转对象,插入数据库 12.Demo地址 ...

  2. SSRF——介绍利用(不全)

    1. SSRF介绍 SSRF(Server-side Request Forge, 服务端请求伪造). 由攻击者构造的攻击链接传给服务端执行造成的漏洞,一般用来在外网探测或攻击内网服务. 2. SSR ...

  3. Linux---必备命令(1)

    文件和目录 # 更改目录位置 cd /tmp # 进入文件夹 cd dirr # 新建文件夹 mkdir dirr # 创建文本 touch text.txt # 显示当前目录下的所有文件,包含已'. ...

  4. LC-35

    题目地址:https://leetcode-cn.com/problems/search-insert-position/ 一样的二分条件,多一个限制插入. 所以思考插入什么位置? 在 [left, ...

  5. [已解决] npm ERR! code EINVALIDPACKAGENAME Invalid package name "": name cannot start with an underscore;

    打开 cmd 输入 `npm update -g` 等待更新完成即可

  6. Java学习day27

    今天跟着做了一个模拟龟兔赛跑的程序 只有一条赛道,乌龟和兔子在同一条赛道上比赛,使用了多线程 为了实现兔子睡觉,在run方法内增加了当前奔跑者是否是兔子的判断且当前奔跑步数是否是10的整数倍的判断,如 ...

  7. 关于 display: inline-block; 中间有间隙的问题

    当我们给一个元素的一系列子元素设置display: inline-block; 时,会发现子元素之间存在间隙,如 <style> div { display: inline-block; ...

  8. HCIE笔记-第五节-IP地址+VLSM

    192.168.1.111 -- 点分十进制 -- IPV4地址表示格式 计算机 只能识别 01010101 二进制 4组十进制数 规则:二进制0/1 在不同位表达的含义是不一致的,0永远代表不取值, ...

  9. ElasticSearch7.3学习(十九)---- deep paging

    1.什么是deep paging 根据相关度评分倒排序,所以分页过深,协调节点会将大量数据聚合分析. 2.deep paging 性能问题 1消耗网络带宽,因为所搜过深的话,各 shard 要把数据传 ...

  10. JS_Window-三种消息框:警告框、确认框、提示框、页面显示时间-计时-延时

    1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="U ...