React Hook学习笔记

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。


什么是Hook?

Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。Hook 不能在 class 组件中使用 —— 这使得你不使用 class 也能使用 React。

React 内置了一些像 useState 这样的 Hook。你也可以创建你自己的 Hook 来复用不同组件之间的状态逻辑。


Hook使用规则

  1. 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。

  2. 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用(除自定义的Hook外)。


State Hook

通常,在class组件中,我们会使用this.state初始化state,例如:

class Example extends React.Component {
  constructor(props) {
    super(props);
    // 将state初始化为一个对象: {count: 0}
    this.state = {
      count: 0
    };
  }

在Hook之前,function组件由于没有this,无法通过this.state的方式获得state。因此当我们发现当前的组件需要用到state时,我们只能将该组件改写为class组件。
Hook为我们提供了一个在function组件中使用state的途径。

import React, { useState } from 'react';

function Example() {
  // 声明一个叫 “count” 的 state 变量,该变量的初始值为0
  const [count, setCount] = useState(0);

useState方法的工作原理是:定义一个state变量,该变量在useState方法结束之后依然会被React保留。接下来会通过分析该方法的返回值和参数,详细解释该方法的使用方式。

  • 参数:useState会接收一个参数,这个参数就是该方法创建的state变量的初始值。这个初始值可以是数字或字符串或者是对象。在示例中,这个初始值为0。

  • 返回值:useState方法会提供两个返回值,第一个是创建的state变量的名称,另一个是更新state变量的函数。这个函数与this.setState类似。比如:

// class组件中,更改state的方法
onClick={() => this.setState({ count: this.state.count + 1 })}

// function组件中,用Hook更新state
onClick={() => setCount(count + 1)}
  • 读取state:
// class:
<p>You clicked {this.state.count} times</p>
// hook:
<p>You clicked {count} times</p>

Example: 计时器
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>
    );
  }
}
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>
  );
}


Effect Hook

React的class组件提供的生命周期方法能让我们在dom的不同生命周期,调用一些额外的代码。比如数据获取,设置订阅以及手动更改 React 组件中的DOM,这些额外的代码被称为副作用。

副作用分两种:需要清除的和不需要清除的。这两种副作用在使用hook的过程中,有一些区别。

无需清除的effect

常见的无需清除的effect有发送网络请求,手动变更 DOM,记录日志等。我们在执行完这些操作后,就可以忽略它们了。

class组件中,render函数是不应该有任何副作用的,因此我们会将副作用操作放到对应的生命周期里。比如,在上文的计数器示例中,我们想要为计数器增加一个小功能:将 document 的 title 设置为包含了点击次数的消息。

在class组件中的实现应该是:

// 在组建加载时
componentDidMount() {
  document.title = `You clicked ${this.state.count} times`;
}
// 在组件更新时
componentDidUpdate() {
  document.title = `You clicked ${this.state.count} times`;
}

而如果我们使用hook的实现应该是:

useEffect(() => {
  document.title = `You clicked ${count} times`;
});

useEffect就是hook为我们提供的代替生命周期的方法,它的工作原理是,React 会保存你传递的函数(我们将它称之为 “effect”),并且在执行 DOM 更新之后调用它。
值得注意的是,与生命周期函数区分开挂载与更新不同,useEffect中的方法,会在dom的每一次变更之后执行,不管是第一次,还是第n次。

需要清除的effect

在开发中,为了防止敏感信息的泄露哦,有一些副作用是需要清除的,例如订阅外部数据源。
如果在计数器中,加入一个订阅好友在线状态的新功能。当我们
在class中,清除的操作可以如下实现:

// 在挂载时,调用 订阅好友的在线状态 方法
componentDidMount() {
  ChatAPI.subscribeToFriendStatus(
    this.props.friend.id,
    this.handleStatusChange
  );
}
// 在即将销毁时,调用 取消订阅 方法
componentWillUnmount() {   
  ChatAPI.unsubscribeFromFriendStatus(
    this.props.friend.id,
    this.handleStatusChange
  );
}

可以看到,不管是在挂载还是销毁时,调用的逻辑代码是重复的。
相应的,使用hook的实现方法如下:

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);
  };
});

当DOM更新时,调用一个方法,这个方法中调用了handleStatusChange和订阅方法,返回一个cleanup方法。这个返回的cleanup方法会在每一次DOM变化后清除操作时被调用。


Example:显示好友状态的计数器

功能:

  1. 当点击“Click me”时,更新state,并且在页面展示和title中显示更新后的state;
  2. 从props中读取friend.id,然后在组件挂载后订阅好友的状态,并在卸载组件的时候取消订阅。
class
class FriendStatusWithCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0, isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }

  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  handleStatusChange(status) {
    this.setState({
      isOnline: status.isOnline
    });
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}
hook
function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

通过跳过 Effect 进行性能优化

在上述示例中, state只有一个变量count,但是在现实中,state可能会有很多歌变量,而如果任何一个变量的变化都会重新调用useEffect的话,可能会导致一些性能问题。在这种时候,我们希望这个useEffect函数在且只在它依赖的变量发生变化时被调用。

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

对于有清除操作的 effect 同样适用:

useEffect(() => {
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  return () => {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
}, [props.friend.id]); // 仅在 props.friend.id 发生变化时,重新订阅

useEffect中,第二个参数决定了是否再次执行该effect。

  • 当第二个参数是空数组【】时,effect仅会在挂载和销毁时执行;
  • 当第二个参数为空时,任何state变量的变化都会导致effect的再次执行;
  • 当第二个参数为不为空的数组时,effect会在数组内的任意变量发生变化时执行。

我终于学完了~~~~~🎉

References:

React官方文档:

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容