React Hook 是 v16.8 的新功能,自诞生以来,受到广泛的好评,在 React 版本更新中具有里程碑的意义。现在都2020年了,再不上车 React Hook 就真的 out 了...

Hook 动机

本着“存在即合理”的原则,我们先来康康 Hook 为我们解决了哪些问题?Hook 有哪些优势呢?


在编写 React 组件时,我们更喜欢函数组件,而不是 class 组件。


因为函数组件代码更少,结构更清晰,不容易产生 bug。但是,函数组件没办法使用状态,只能作为展示组件(就是个花瓶...哎)。

有了 Hook,我们就能在函数组件中使用状态了**。毫不夸张的说,以后的组件都可以用函数组件 + Hook 来写。

class 组件的问题:

  • 状态逻辑难复用:在组件之间复用状态逻辑很难,一般会用到 render props (渲染属性)或者 HOC高阶组件)。但无论是渲染属性,还是高阶组件,都会在原先的组件外包裹一层父容器(一般都是 div 元素),导致层级冗余
  • 生命周期
    • 多而善变的生命周期:用 React 开发的你一定被那些杂乱多变的生命周期恶心过,声明周期多就不说了,随着版本的变动会经常变化,导致升级 React 版本时很烦
    • 生命周期逻辑混乱:在生命周期函数中混杂不相干的逻辑(如:在 componentDidMount 中注册事件以及其他的逻辑,在 componentWillUnmount 中卸载事件,这样分散不集中的写法,很容易写出 bug )
    • class 组件难以拆分:class 组件中到处都是对状态的访问和处理,导致组件难以拆分成更小的组件
  • this 指向问题:class 组件中的 this 指向问题绝对让人头疼,需要我们手动小心翼翼地去绑定 this,一不小心就会出现 bug。

Hook 优势:

  • 优化 class 组件的问题
  • 能在无需修改组件结构的情况下复用状态逻辑(自定义 Hook )
  • 能将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据)
  • 副作用的关注点分离副作用指那些没有发生在数据向视图转换过程中的逻辑,如 ajax 请求、访问原生dom 元素、本地持久化缓存、绑定/解绑事件、添加订阅、设置定时器、记录日志等。以往这些副作用都是写在类组件生命周期函数中的。而 useEffect 在全部渲染完毕后才会执行,useLayoutEffect 会在浏览器 layout 之后,painting 之前执行

Hook 规则

Hook 可以让你在不编写 class 组件的情况下使用 state 以及其他的 React 特性。但是,有些规则是我们需要准守的:

  • 只能在函数内部的最外层调用 Hook
    不要在循环,条件或嵌套函数中调用 Hook,
     确保总是在你的 React 函数的最顶层调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确
  • 只能在 React 的函数组件(非 class组件)中调用 Hook
    不要在普通的 JavaScript 函数或 class 组件中调用 Hook。
    你可以:
    • 在 React 的函数组件中调用 Hook
    • 在自定义 Hook 中调用其他 Hook

ok,到此为止,我们已经了解 Hook 有哪些优势了。


下面,我们开始认识最常用的两个 Hook API—— useState、useEffect。


这两个API很好理解,而且很实用,弄懂能处理80%的业务场景。本文不会涉及太复杂的操作,仅仅作为入门。

useState

先来看一段简单的 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>
);
}

我们将通过将这段代码与一个等价的 class 示例进行比较来开始学习 Hook。

等价的 class 组件示例:

class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
} render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}

很简单的一个加1计数器,Hook 写法比 class 组件是不是简洁了很多。


下面,我们来分析如何使用 useState Hook...

// 第一步:从 react 库中引入 useState Hook
import React, { useState } from 'react'; function Example() {
/* 第二步:通过调用 useState Hook 声明了一个新的 state 变量。
* 它返回一对值(数组)解构到我们命名的变量上。
* 第一个返回的是状态 count,它存储的是点击次数。我们通过传 0 作为 useState 唯一的参数来将其初始化 0。
* 第二个返回的值本身就是一个函数。它让我们可以更新 count 的值,所以我们叫它 setCount。
*/
const [count, setCount] = useState(0); // 声明一个叫 "count" 的 state 变量 return (
<div>
// 第三步:读取 state,即count
<p>You clicked {count} times</p>
// 第四步:更新 state,通过 setCount()
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

通过上面的分析,我们可以看到使用 useState Hook 管理状态简直太爽了。不用写繁琐的 class 组件,不用担心 this 指向,代码是如此的清晰。


如何使用多个 state 变量:
将 state 变量声明为一对 [something, setSomething] 也很方便,因为如果我们想使用多个 state 变量,它允许我们给不同的 state 变量取不同的名称:

function ExampleWithManyStates() {
// 声明多个 state 变量
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: '学习 Hook' }]);
...

在以上组件中,我们有局部变量 agefruit 和 todos,并且我们可以单独更新它们:

  function handleOrangeClick() {
// 和 this.setState({ fruit: 'orange' }) 类似
setFruit('orange');
}

不必使用多个 state 变量。State 变量可以很好地存储对象和数组,因此,你仍然可以将相关数据分为一组。然而,不像 class 中的 this.setState,更新 state 变量总是_替换_它而不是合并它。

注意

你可能想知道:为什么叫 useState 而不叫 createState?

“Create” 可能不是很准确,因为state 只在组件首次渲染的时候被创建。在下一次重新渲染时,useState 返回给我们当前的 state。否则它就不是 “state”了!这也是 Hook 的名字_总是_以 use开头的一个原因。

useEffect

Effect Hook定义:useEffect 传入一个 callback 函数

useEffect(effect: React.EffectCallback, deps?: ReadonlyArray<any> | undefined)

Effect Hook作用:处理函数组件中的副作用,如异步操作、延迟操作等,可以替代Class Component的componentDidMountcomponentDidUpdatecomponentWillUnmount等生命周期。

Effect Hook特性:

  • effect(副作用):指那些没有发生在数据向视图转换过程中的逻辑,如 ajax 请求、访问原生dom 元素、本地持久化缓存、绑定/解绑事件、添加订阅、设置定时器、记录日志等。
  • 副作用操作可以分两类:需要清除的和不需要清除的。
  • useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它和 class 组件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 具有相同的用途,只不过被合并成了一个 API
  • useEffect 接收一个函数,该函数会在组件渲染到屏幕之后才执行。该函数有要求:要么返回一个能清除副作用的函数,要么就不返回任何内容
  • componentDidMountcomponentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用,其 API 与 useEffect 相同

useEffect 使用示例:

import React, { useState, useEffect } from 'react';

function Example() {
const [count, setCount] = useState(0); // 类似 componentDidMount 和 componentDidUpdate
useEffect(() => {
// 使用浏览器 API 去更新 document 标题
document.title = `You clicked ${count} times`;
}); // 类似 componentDidMount
useEffect(() => {
// 使用浏览器 API 去更新 document 标题
document.title = `You clicked ${count} times`;
}, []); // 慎用!监听空数组,当 callback 使用到 state 或 props 时最好不要用,因为只能获取初始化的数据 // 返回一个函数用于清除操作
useEffect(() => {
document.title = `You clicked ${count} times`; window.addEventListener('load', loadHandle); // loadHandle 函数定义省略 return () => {
window.removeEventListener('load', loadHandle); // 执行清理:callback 下一次执行前调用
};
}, [count]); // 只有当count的值发生变化时,才会重新执行 callback return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

useEffect 用法很简单,但是有两个地方需要特别注意:

  • deps 参数很重要

    • useEffect 可以接受第二个参数 deps,用于在 re-render 时判断是否重新执行 callback
    • deps 数组项必须是 immutable 的,比如:不能也不必传 useRef、dispatch 等
    • deps 的比较是浅比较(参阅源码),传入对象、函数是无意义
    • 作为最佳实践,使用 useEffect 时请尽可能都传 deps
  • 清除副作用
    • useEffect 传入的 callback 要么返回一个清除副作用的函数,要么什么都不返回。所以,callback 不能用 async 函数(面试题:如何在 useEffect 中使用 async 函数)
    • useEffect 传入的 callback 返回一个函数,在下一次执行 callback 前将会执行这个函数,从而达到清理 effect 的效果

useEffect 的用法大概就是这样的,有一些坑和更复杂操作这里没有涉及。当然,要深入理解的话需要去啃源码了,这里不做过多的解释。

Hook 核心知识点:闭包

当你在使用 Hook 遇到问题时,请先考虑是否由于闭包引起的。这将帮助你快速排查问题。

总结

Hook 让我们可以在函数组件中使用状态state,函数组件一统 React 的时代来了,这很棒。


Hook 可以让我们摒弃那些繁琐的生命周期、不用考虑 this 的指向、复用逻辑也不用写HOC了,这很棒。


Hook 还有更多 API 等着我们去探索,同时也支持自定义 Hook。


Hook 发车啦,用过都说好...


参考:
Hook 官方文档
30分钟精通React Hooks
React Hooks完全上手指南

React Hook上车的更多相关文章

  1. React Hook挖坑

    React Hook挖坑 如果已经使用过 Hook,相信你一定回不去了,这种用函数的方式去编写有状态组件简直太爽啦. 如果还没使用过 Hook,那你要赶紧升级你的 React(v16.8+),投入 H ...

  2. [React] Detect user activity with a custom useIdle React Hook

    If the user hasn't used your application for a few minutes, you may want to log them out of the appl ...

  3. 使用React Hook后的一些体会

    一.前言 距离React Hook发布已经有一段时间了,笔者在之前也一直在等待机会来尝试一下Hook,这个尝试不是像文档中介绍的可以先在已有项目中的小组件和新组件上尝试,而是尝试用Hook的方式构建整 ...

  4. React Hook 学习

    1.官方文档 https://react.docschina.org/docs/hooks-intro.html 2.阮一峰 reactHook http://www.ruanyifeng.com/b ...

  5. React Hook:使用 useEffect

    React Hook:使用 useEffect 一.描述 二.需要清理的副作用 1.在 class 组件中 2.使用 effect Hook 的示例 1.useEffect 做了什么? 2.为什么在组 ...

  6. GraphQL + React Apollo + React Hook + Express + Mongodb 大型前后端分离项目实战之后端(19 个视频)

    GraphQL + React Apollo + React Hook + Express + Mongodb 大型前后端分离项目实战之后端(19 个视频) GraphQL + React Apoll ...

  7. GraphQL + React Apollo + React Hook 大型项目实战(32 个视频)

    GraphQL + React Apollo + React Hook 大型项目实战(32 个视频) GraphQL + React Apollo + React Hook 大型项目实战 #1 介绍「 ...

  8. 【译】值得推荐的十大React Hook 库

    十大React Hook库 原文地址:https://dev.to/bornfightcompany/top-10-react-hook-libraries-4065 原文作者:Juraj Pavlo ...

  9. React Hook 入门使用

    React Hook 是什么 1.没有比官网说的更好的 HOOK 1. React Hook 官方 2. 用我们自己的话说,它是一个钩子函数,用来处理组件间的状态的一个方法,暂时理解为一个高阶函数吧. ...

随机推荐

  1. 分析Java中的length和length()

    在不适用任何带有自动补全功能的IDE的情况下,我们怎么获取一个数组的长度?如何获取字符串的长度? 这里我们先举用实例去分析一下:int[] arr=new int[3]:System.out.prin ...

  2. 如何应对HR小姐姐的千年历史遗留问题:你为什么从上家公司离职?

    最近找我询问面试问题的学生比较多,而且问的问题基本上都是课堂上讲过的,好吧,在此心疼自己三秒钟. 那么今天就为各位宝宝们整理一下,如何优雅的回复HR小姐姐的这个千年历史遗留问题:你为什么从上家公司离职 ...

  3. 使用 KM 处理 HHKB 方向键

    对于上了 HHKB 这条贼船的人来说,刚开始使用起来最大的别扭可能就是没有方向键的问题了. 最早的我使用 Karabiner 来解决,里边有一些内置的组合可以替代方向键,我用 control + hj ...

  4. Android长按及拖动事件探究

    Android中长按拖动还是比较常见的.比如Launcher中的图标拖动及屏幕切换,ListView中item顺序的改变,新闻类App中新闻类别的顺序改变等.下面就这个事件做一下分析. 就目前而言,A ...

  5. 为何银行愿为收购supercell做无权追索融资?

    无追索权融资又称纯粹的项目融资,是指贷款人对项目主办人没有任何追索权的项目融资.简单来说,这是一种项目失败,也无法追尝的承诺,一般发生在石油.天然气.煤炭.铜.铝等矿产资源开发等相对较为保值的项目融资 ...

  6. 安卓权威编程指南 挑战练习 25章 深度优化 PhotoGallery 应用

    你可能已经注意到了,提交搜索时, RecyclerView 要等好一会才能刷新显示搜索结果.请接受挑战,让搜索过程更流畅一些.用户一提交搜索,就隐藏软键盘,收起 SearchView 视图(回到只显示 ...

  7. 【深入理解Java虚拟机 】类加载器的命名空间以及类的卸载

    类加载器的命名空间 每个类加载器又有一个命名空间,由其以及其父加载器组成 类加载器的命名空间的作用和影响 每个类加载器又有一个命名空间,由其以及其父加载器组成 在每个类加载器自己的命名空间中不能出现相 ...

  8. 使用Taiko + Gauge进行自动化测试(一)

    目录 初识Taiko 环境安装 尝试Taiko taiko 执行过程 结合Gauge编写用例 使用Gauge 总结 初识Taiko 先来了解一下什么是Taiko:"Taiko是一个免费的开源 ...

  9. LeetCode--二叉树2--运用递归解决树的问题

    LeetCode--二叉树2--运用递归解决树的问题 在前面的章节中,我们已经介绍了如何利用递归求解树的遍历. 递归是解决树的相关问题最有效和最常用的方法之一. 我们知道,树可以以递归的方式定义为一个 ...

  10. paillier加密算法原理详解

    paillier加密算法是一种公钥加密算法,基于复合剩余类的困难问题.满足加法同态,即密文相乘等于明文相加:D(E(m1)·E(m2))=m1+m2.这里详细介绍其加密解密是如何推导的,需要具备数论. ...