惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Check Point Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
C
CERT Recently Published Vulnerability Notes
Apple Machine Learning Research
Apple Machine Learning Research
酷 壳 – CoolShell
酷 壳 – CoolShell
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
J
Java Code Geeks
Jina AI
Jina AI
雷峰网
雷峰网
M
MIT News - Artificial intelligence
小众软件
小众软件
H
Help Net Security
The Register - Security
The Register - Security
T
Tailwind CSS Blog
D
DataBreaches.Net
大猫的无限游戏
大猫的无限游戏
有赞技术团队
有赞技术团队
G
Google Developers Blog
云风的 BLOG
云风的 BLOG
Google DeepMind News
Google DeepMind News
月光博客
月光博客
Project Zero
Project Zero
P
Proofpoint News Feed
S
Security @ Cisco Blogs
L
LINUX DO - 最新话题
I
InfoQ
Vercel News
Vercel News
V
Vulnerabilities – Threatpost
S
Schneier on Security
Spread Privacy
Spread Privacy
Hugging Face - Blog
Hugging Face - Blog
D
Docker
博客园 - 【当耐特】
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
The Blog of Author Tim Ferriss
博客园 - 聂微东
宝玉的分享
宝玉的分享
Recorded Future
Recorded Future
K
Kaspersky official blog
L
LINUX DO - 热门话题
Stack Overflow Blog
Stack Overflow Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
腾讯CDC
A
About on SuperTechFans
D
Darknet – Hacking Tools, Hacker News & Cyber Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
GbyAI
GbyAI
Schneier on Security
Schneier on Security
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
B
Blog RSS Feed

卡瓦邦噶!

服务器高性能网络调优 | 卡瓦邦噶! 为何写作 | 卡瓦邦噶! 读《金阁寺》 | 卡瓦邦噶! 雨季又来 | 卡瓦邦噶! MTU Probe 引起的初始延迟 | 卡瓦邦噶! 3.5 秒的固定延迟问题 | 卡瓦邦噶! 学习网络的一点经验 | 卡瓦邦噶! ARP 问题诊断 | 卡瓦邦噶! 网络断断续续…… | 卡瓦邦噶! Piccolo P2P 镜像分发 | 卡瓦邦噶! 一起看电影 | 卡瓦邦噶! 《征服C指针》 | 卡瓦邦噶! 我的姥姥 | 卡瓦邦噶! Python的哲学 Python 3.5的新特性 学校不教的计算机课 垃圾回收(GC)的三种基本方式 在编程中体验纯粹的快乐 从《美丽新世界》谈自由 在快钱实习 迷人的嗓音和迷人的故事——《Sleepyhead》 Python 的十个自然语言处理工具 记一个愚蠢的bug 一年炉石传说的游戏体验 《以撒的结合:重生》网页版图鉴 分清 C++的指针、引用和数组 笑话三则 自由比皇帝更伟大——《悲惨世界》笔记 Git 10 周年访谈:Linus 讲述背后故事 用 0x3f3f3f3f 设定最大int值的优点 Joel给计算机系学生的建议 一个词法分析器的简单实现 怎样才算健康的生活方式 MacVim 配置攻略 学习培训课程的视频效果好吗? 语言的控制 可悲的大多数 CSS样式思维导图 2014年终总结 奥巴马成为首位写程序的美国总统 青岛老城区的下水道好在哪里? 做优秀 UI 的七个建议(第二部分) 选择爱情的骑士 Git简明教程 Java集合总览 读 《1984》 java问答:终极父类(六)——等待/唤醒和接口 Java 问答:终极父类(五)——toString() Hyperlapse快速视频背后的技术细节 平庸之恶 Java程序员须知的七个日志管理工具 苏州 逛书摊随想 关于考试作弊 你的工作不仅仅是编程 推荐在线学习Java的英文资源 关注女性命运——《千禧年三部曲》 用好你的幻灯片——《演说之禅》 使用ReentrantLock和Lambda表达式让同步更纯净 新手学编程,从哪里开始? 程序员都是工程师吗? 不要学习代码,要学会思考 Java 问答:终极父类(四)——hashCode() Java 问答:终极父类(三)——finalize()和 getClass() 程序员职业之路的选择 我是一名摄影家 写给何小树的城市指南 为什么一些语言会比别的快? Java的常见误区与细节 跟朋友在一起玩游戏 死神永生——读《三体》 五种类型的程序员 创业圣经——读《黑客与画家》 纪念加西亚·马尔克斯 Junit中处理异常的另一种方式:catch-exception Java8采用Martin Fowler的方法创建内部DSL Linux HotSopt虚拟机GC线程的CPU占用率 J2EE概念介绍 如何成为一名黑客 Java 问答:终极父类(二)——equals()方法 为什么我喜欢Java Java 问答:终极父类(一)——clone()方法 七个改变世界的Java项目 java中默认类型转换的小问题 传统与创新 欢迎来到互联网 莫言和马尔克斯——读《生死疲劳》 位运算的妙用 读《人为什么活着》 写博客教会我的事情 中国特色操作系统 2013年总结 《永不妥协》影评 恨不相逢未嫁时——《廊桥遗梦》影评 如何优雅地使用PPT 给明年依然年轻的我们 天才与柱子 黑客守则和黑客精神 Looking for Freedom——《被解救的姜戈》 简洁之道
React Hooks 基础教程
laixintao · 2021-01-24 · via 卡瓦邦噶!

最近在写一个前端页面,看了一个 UI 框架的 Example,发现已经看不懂 React 了。上次写 React 的时候,还都是用 Class Based Component, 我还花了一些时间弄明白一个组件的“生命周期”,没想到这么短的时间,已经不流行使用 Class 写组件了。现在用一个叫 React Hooks 的东西,可以通过函数写出来组件。为了看懂现在的 React 代码,我又去学了 Hooks,这边文章来总结我对 Hooks 的理解。

Hooks 就是一些 React 提供的内置函数,通过 Hooks 就可以在 Function 中操作组件的状态(state)了。在我看来解决了两个问题:

  1. Function 中是不可以定义 state 的,所以以前 Function 只能用来写 stateless 的组件,如果有一天你觉得这个 stateless 的组件要加入状态了,那就必须把它先变成 Class Base Component 才行;
  2. Class 组件本身也有问题,它这个设计是要求开发者按照组件的生命周期来写代码,constructor() -> componentDidMount() -> componentWillUpdate() 这种方式,按照 React 的逻辑来组织代码,而不是按照代码本身表达的业务逻辑来组织代码。以前每次写一个新的组件的时候,我都要依靠 Vim 的模板功能生成一个代码模板,但是现在不需要了。写 Hooks 几乎没有模板代码。

下面这个例子来自官方的文档。

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

import React, { useState } from 'react';

function Example() {

  // Declare a new state variable, which we'll call "count"

  const [count, setCount] = useState(0);

  return (

    <div>

      <p>You clicked {count} times</p>

      <button onClick={() => setCount(count + 1)}>

        Click me

      </button>

    </div>

  );

}

在这个例子中,使用 useState() ,让这个组件 “hook” 了一个 React 的 state:count 。count 的初始值就是 useState() 的参数,即0. 如果要改变 count 的值,就使用 setCount 这个函数。(之前是使用 this.setState() )。

可以这么理解,useState() 函数就关联了一个 React 的 state,调用 useState() 会给你 state 的应用,以及更新它的方法。(Hooks 都使用 “use” 开头,为什么不使用 “create” 呢?因为它只是和一个 state “关联”起来了,只是告诉 React “使用”这个 state。只有在组件第一次 render 的时候才会创建这个 state,在后续的更新中并不会创建了。)

另一个 React 提供的 hook 是 useEffect() 。如它的名字,这个 hook 不是和 state 有关的,而是产生 “effect” 的。类似于 React Class 中的 componentDidMount() 或者 componentWillUpdate() 中。

比如下面这段代码:

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

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

function Example() {

  const [count, setCount] = useState(0);

  // Similar to componentDidMount and componentDidUpdate:

  useEffect(() => {

    // Update the document title using the browser API

    document.title = `You clicked ${count} times`;

  });

  return (

    <div>

      <p>You clicked {count} times</p>

      <button onClick={() => setCount(count + 1)}>

        Click me

      </button>

    </div>

  );

}

组件第一次更新的时候会调用 useEffect() 收到的函数,并且以后这个组件每次 render 的时候也会调用:更新页面的标题。

可以看到这里面没有和 React 组件生命周期相关的函数名字。这意味着我们可以根据业务逻辑来组织代码,可以将 effect state 等相关的逻辑放在一起,而不是把多个不相关的业务逻辑,都放到 componentDidMount() 里面去。

之前的这种按照组件生命周期来组织代码的方式,很容易出 Bug。比如,下面是一段正确的代码。这个组件订阅了朋友的在线状态,当组件 Unmount 的时候,会需要取消对这个朋友的状态订阅:

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

class FriendStatus extends React.Component {

  constructor(props) {

    super(props);

    this.state = { isOnline: null };

    this.handleStatusChange = this.handleStatusChange.bind(this);

  }

  componentDidMount() {

    ChatAPI.subscribeToFriendStatus(

      this.props.friend.id,

      this.handleStatusChange

    );

  }

  componentWillUnmount() {

    ChatAPI.unsubscribeFromFriendStatus(

      this.props.friend.id,

      this.handleStatusChange

    );

  }

  handleStatusChange(status) {

    this.setState({

      isOnline: status.isOnline

    });

  }

  render() {

    if (this.state.isOnline === null) {

      return 'Loading...';

    }

    return this.state.isOnline ? 'Online' : 'Offline';

  }

}

但其实 componentWillUnmount 这个函数非常容易忘记(尤其是在逻辑越来越多的情况下),造成内存泄漏。

如果用 useEffect() 来实现的话,就比较清晰了,因为这个函数接受的参数是 Effect 的函数,Effect 函数的返回值可以是一个 clean up 的函数。(好像比较绕,这个设计确实有些奇怪,为什么不将 clean up 的函数作为 useEffect 的第二个参数呢?)

useEffect() 来写的话,就是下面这种形式:

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

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

function FriendStatus(props) {

  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {

    function handleStatusChange(status) {

      setIsOnline(status.isOnline);

    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);

    // Specify how to clean up after this effect:

    return function cleanup() {

      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);

    };

  });

  if (isOnline === null) {

    return 'Loading...';

  }

  return isOnline ? 'Online' : 'Offline';

}

把更新的函数传给 useEffect() ,然后在自己的函数里面返回一个 callback 用来 cleanup. 因为这些逻辑都是在一起的,所以更加不容易忘记。

另外,useEffect() 会在组件 Mount 以及每次更新的时候都运行,相当于 componentDidMount() 和 componentWillMount() 两个函数合起来了。

Hooks 的原理

一个函数中可以使用 Hooks 多次,用来关联不同的 state,比如下面这段代码:

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

function Form() {

  // 1. Use the name state variable

  const [name, setName] = useState('Mary');

  // 2. Use an effect for persisting the form

  useEffect(function persistForm() {

    localStorage.setItem('formData', name);

  });

  // 3. Use the surname state variable

  const [surname, setSurname] = useState('Poppins');

  // 4. Use an effect for updating the title

  useEffect(function updateTitle() {

    document.title = name + ' ' + surname;

  });

  // ...

}

但是在调用 useState() 的时候并没有告诉 React name 是和哪一个 State 来关联,React 是怎么知道的呢?

答案是调用顺序,你按照这个顺序使用 Hooks,React 就按照这个顺序给你赋予这些 state 的值,“绑定”的过程类似下面这样:

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

// ------------

// First render

// ------------

useState('Mary')           // 1. Initialize the name state variable with 'Mary'

useEffect(persistForm)     // 2. Add an effect for persisting the form

useState('Poppins')        // 3. Initialize the surname state variable with 'Poppins'

useEffect(updateTitle)     // 4. Add an effect for updating the title

// -------------

// Second render

// -------------

useState('Mary')           // 1. Read the name state variable (argument is ignored)

useEffect(persistForm)     // 2. Replace the effect for persisting the form

useState('Poppins')        // 3. Read the surname state variable (argument is ignored)

useEffect(updateTitle)     // 4. Replace the effect for updating the title

所以顺序至关重要,知道这一点,就可以避免一些错误的使用方法。比如,一个原则是,只在 Component 的最顶层使用 Hooks,假如你没有函数的最顶层使用 Hooks,而是在嵌套结构(比如循环或者 if block 中)使用,那么绑定的时候就会出问题。

比如像下面这样:

JavaScript

1

2

3

4

5

6

// 🔴 We're breaking the first rule by using a Hook in a condition

  if (name !== '') {

    useEffect(function persistForm() {

      localStorage.setItem('formData', name);

    });

  }

那在 React 实际绑定组件的内部状态的时候,就会乱掉:

JavaScript

1

2

3

4

useState('Mary')           // 1. Read the name state variable (argument is ignored)

// useEffect(persistForm)  // 🔴 This Hook was skipped!

useState('Poppins')        // 🔴 2 (but was 3). Fail to read the surname state variable

useEffect(updateTitle)     // 🔴 3 (but was 4). Fail to replace the effect

另一个原则是,只在 React Function Components 里面使用 Hooks。如果只在 Function Component 里面调用 Hooks 的话,你看到一个 Component 就会知道里面的 State 的变化,但是如果状态还在 Component 外面被控制,那么就很难管理了。并且 React Hooks 应该也不会在 Component 之外去帮你管理这些状态。

定义自己的 Hooks

没有 Hooks 以前,如果要抽象一部分涉及 state 的代码出来复用的话,只能再写一个 Class Component,现在可以用 Hooks 了。我们可以定义一个自己的 Hook。

比如,重用一段 Friend 订阅上下线的逻辑:

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

import { useState, useEffect } from 'react';

function useFriendStatus(friendID) {

  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {

    function handleStatusChange(status) {

      setIsOnline(status.isOnline);

    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);

    return () => {

      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);

    };

  });

  return isOnline;

}

Hooks 里面会调用其他的 Hook,当然,也只能在最顶层来调用

我们自己定义的 Hooks 不像是 React 内置的那些一样参数都是固定的,Hooks 本质上就是调用了其他的 Hooks 的函数,所以我们可以自定义自己的参数和返回值:

  • 订阅一个朋友的状态:所以接收的参数是 FriendId
  • 提供的是朋友的状态,所以返回一个 onlineStatus

在 React 的视角,即使你 call 的是你自己定义的 Hooks,但是最终里面,还是调用的 React 定义的 Hooks。所以最终,你都只调用了 React 提供的 Hooks。就像我们所有的程序调用的函数最终只调用到了系统提供给我们的函数一样。

参考资料:

  1. https://reactjs.org/docs/hooks-intro.html