欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

React Hooks究竟是什么呢?

程序员文章站 2022-05-04 12:23:14
摘要: React Hooks原理解析。 原文: "快速了解 React Hooks 原理" 译者:前端小智 我们大部分 React 类组件可以保存状态,而函数组件不能? 并且类组件具有生命周期,而函数组件却不能? React 早期版本,类组件可以通过继承 来优化一些不必要的渲染,相对于函数组件,R ......

摘要: react hooks原理解析。

我们大部分 react 类组件可以保存状态,而函数组件不能? 并且类组件具有生命周期,而函数组件却不能?

react 早期版本,类组件可以通过继承purecomponent来优化一些不必要的渲染,相对于函数组件,react 官网没有提供对应的方法来缓存函数组件以减少一些不必要的渲染,直接 16.6 出来的 react.memo函数。

react 16.8 新出来的hook可以让react 函数组件具有状态,并提供类似 componentdidmountcomponentdidupdate等生命周期方法。

类被会替代吗?

hooks不会替换类,它们只是一个你可以使用的新工具。react 团队表示他们没有计划在react中弃用类,所以如果你想继续使用它们,可以继续用。

我能体会那种总有新东西要学的感觉有多痛苦,不会就感觉咱们总是落后一样。hooks 可以当作一个很好的新特性来使用。当然没有必要用 hook 来重构原来的代码, react团队也建议不要这样做。

go go

来看看hooks的例子,咱们先从最熟悉的开始:函数组件。

以下 onetimebutton 是函数组件,所做的事情就是当我们点击的时候调用 sayhi 方法。

    import react from 'react';
    import { render } from 'react-dom';
    
    function onetimebutton(props) {
      return (
        <button onclick={props.onclick}>
            点我点我
        </button>
      )
    }
    
    function sayhi() {
      console.log('yo')
    }
    
    render(
      <onetimebutton onclick={sayhi}/>,
      document.queryselector('#root')
    )

我们想让这个组件做的是,跟踪它是否被点击,如果被点击了,禁用按钮,就像一次性开关一样。

但它需要一个state,因为是一个函数,它不可能有状态(react 16.8之前),所以需要重构成类。

函数组件转换为类组件的过程中大概有5个阶段:

  • 否认:也许它不需要是一个类,我们可以把 state 放到其它地方。
  • 实现: 废话,必须把它变成一个class,不是吗?
  • 接受:好吧,我会改的。
  • 努力加班重写:首先 写 class thing extends react.component,然后 实现 render等等 。
  • 最后:添加state。

    class onetimebutton extends react.component {
      state = {
        clicked: false
      }
    
      handleclick = () => {
        this.props.onclick();
    
        // ok, no more clicking.
        this.setstate({ clicked: true });
      }
    
      render() {
        return (
          <button
            onclick={this.handleclick}
            disabled={this.state.clicked}
          >
            you can only click me once
          </button>
        );
      }
    }

这是相当多的代码,组件的结构也发生了很大的变化, 我们需要多个小的功能,就需要改写很多。

使用 hook 轻松添加 state

接下来,使用新的 usestate hook向普通函数组件添加状态:

    import react, { usestate } from 'react'
    
    function onetimebutton(props) {
      const [clicked, setclicked] = usestate(false)
      
      function doclick() {
        props.onclick();
        setclicked(true)
      }
    
      return (
        <button
          onclick={clicked ? undefined : doclick}
          disabled={clicked}
        >
          点我点我
        </button>
      )
    }

这段代码是如何工作的

这段代码的大部分看起来像我们一分钟前写的普通函数组件,除了usestate

usestate是一个hook。 它的名字以“use”开头(这是hooks的规则之一 - 它们的名字必须以“use”开头)。

usestate hook 的参数是 state 的初始值,返回一个包含两个元素的数组:当前state和一个用于更改state 的函数。

类组件有一个大的state对象,一个函数this.setstate一次改变整个state对象。

函数组件根本没有状态,但usestate hook允许我们在需要时添加很小的状态块。 因此,如果只需要一个布尔值,我们就可以创建一些状态来保存它。

由于hook以某种特殊方式创建这些状态,并且在函数组件内也没有像setstate函数来更改状态,因此 hook 需要一个函数来更新每个状态。 所以 usestate 返回是一对对应关系:一个值,一个更新该值函数。 当然,值可以是任何东西 - 任何js类型 - 数字,布尔值,对象,数组等。

现在,你应该有很多疑问,如:

  • 当组件重新渲染时,每次都不会重新创建新的状态吗? react如何知道旧状态是什么?
  • 为什么hook 名称必须以“use”开头? 这看起来很可疑。
  • 如果这是一个命名规则,那是否意味着我可以自定义 hook。
  • 如何存储更复杂的状态,很多场景不单单只有一个状态值这么简单。

hooks 的魔力

将有状态信息存储在看似无状态的函数组件中,这是一个奇怪的悖论。这是第一个关于钩子的问题,咱们必须弄清楚它们是如何工作的。

原作者得的第一个猜测是某种编译器的在背后操众。搜索代码usewhatever并以某种方式用有状态逻辑替换它。

然后再听说了调用顺序规则(它们每次必须以相同的顺序调用),这让我更加困惑。这就是它的工作原理。

react第一次渲染函数组件时,它同时会创建一个对象与之共存,该对象是该组件实例的定制对象,而不是全局对象。只要组件存在于dom中,这个组件的对象就会一直存在。

使用该对象,react可以跟踪属于组件的各种元数据位。

请记住,react组件甚至函数组件都从未进行过自渲染。它们不直接返回html。组件依赖于react在适当的时候调用它们,它们返回的对象结构react可以转换为dom节点。

react有能力在调用每个组件之前做一些设置,这就是它设置这个状态的时候。

其中做的一件事设置 hooks 数组。 它开始是空的, 每次调用一个hook时,react 都会向该数组添加该 hook

为什么顺序很重要

假设咱们有以下这个组件:

    function audioplayer() {
      const [volume, setvolume] = usestate(80);
      const [position, setposition] = usestate(0);
      const [isplaying, setplaying] = usestate(false);
    
      .....
    }

因为它调用usestate 3次,react 会在第一次渲染时将这三个 hook 放入 hooks 数组中。

下次渲染时,同样的3hooks以相同的顺序被调用,所以react可以查看它的数组,并发现已经在位置0有一个usestate hook ,所以react不会创建一个新状态,而是返回现有状态。

这就是react能够在多个函数调用中创建和维护状态的方式,即使变量本身每次都超出作用域。

多个usestate 调用示例

让咱们更详细地看看这是如何实现的,第一次渲染:

  1. react 创建组件时,它还没有调用函数。react 创建元数据对象和hooks的空数组。假设这个对象有一个名为nexthook的属性,它被放到索引为0的位置上,运行的第一个hook将占用位置0

  2. react 调用你的组件(这意味着它知道存储hooks的元数据对象)。

  3. 调用usestate,react创建一个新的状态,将它放在hooks数组的第0位,并返回[volume,setvolume]对,并将volume 设置为其初始值80,它还将nexthook索引递增1。
  4. 再次调用usestate,react查看数组的第1位,看到它是空的,并创建一个新的状态。 然后它将nexthook索引递增为2,并返回[position,setposition]
  5. 第三次调用usestate。 react看到位置2为空,同样创建新状态,将nexthook递增到3,并返回[isplaying,setplaying]

现在,hooks 数组中有3个hook,渲染完成。 下一次渲染会发生什么?

  1. react需要重新渲染组件, 由于 react 之前已经看过这个组件,它已经有了元数据关联。
  2. reactnexthook索引重置为0,并调用组件。
  3. 调用usestate,react查看索引0处的hooks数组,并发现它已经在该槽中有一个hook。,所以无需重新创建一个,它将nexthook推进到索引1并返回[volume,setvolume],其中volume仍设置为80
  4. 再次调用usestate。 这次,nexthook1,所以react检查数组的索引1。同样,hook 已经存在,所以它递增nexthook并返回[position,setposition]
  5. 第三次调用usestate,我想你知道现在发生了什么。

就是这样了,知道了原理,看起来也就不那么神奇了, 但它确实依赖于一些规则,所以才有使用 hooks 规则。

hooks 的规则

只需要遵守规则 3 :它们的名称必须以“use”为前缀。

例如,我们可以从audioplayer组件中将3个状态提取到自己的自定义钩子中:

    function audioplayer() {
      // extract these 3 pieces of state:
      const [volume, setvolume] = usestate(80);
      const [position, setposition] = usestate(0);
      const [isplaying, setplaying] = usestate(false);
    
      // < beautiful audio player goes here >
    }

因此,咱们可以创建一个专门处理这些状态的新函数,并使用一些额外的方法返回一个对象,以便更容易启动和停止播放,例如:

    function useplayerstate(lengthofclip) {
      const [volume, setvolume] = usestate(80);
      const [position, setposition] = usestate(0);
      const [isplaying, setplaying] = usestate(false);
    
      const stop = () => {
        setplaying(false);
        setposition(0);
      }
    
      const start = () => {
        setplaying(true);
      }
    
      return {
        volume,
        position,
        isplaying,
        setvolume,
        setposition,
        start,
        stop
      };
    }

像这样提取状态的一个好处是可以将相关的逻辑和行为组合在一起。可以提取一组状态和相关事件处理程序以及其他更新逻辑,这不仅可以清理组件代码,还可以使这些逻辑和行为可重用。

另外,通过在自定义hooks中调用自定义hooks,可以将hooks组合在一起。hooks只是函数,当然,函数可以调用其他函数。

总结

hooks 提供了一种新的方式来处理react中的问题,其中的思想是很有意思且新奇的。

react团队整合了一组很棒的和一个常见,从是否需要重写所有的类组件到钩hooks是否因为在渲染中创建函数而变慢? 以及两者之间的所有东西,所以一定要看看。

代码部署后可能存在的bug没法实时知道,事后为了解决这些bug,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的bug监控工具 fundebug

原文:

关于fundebug

fundebug专注于javascript、微信小程序、微信小游戏、支付宝小程序、react native、node.js和java线上应用实时bug监控。 自从2016年双十一正式上线,fundebug累计处理了20亿+错误事件,付费客户有阳光保险、核桃编程、荔枝fm、掌门1对1、微脉、青团社等众多品牌企业。欢迎大家!