【译】什么是React Hooks

32
原文:What are React Hooks?
作者:Robin Wieruch
译者:博轩

react

React Hooks2018年10月的React Conf 中引入,作为在 React 函数组件中使用状态和生命周期的一种方法。虽然函数组件之前被称为 无状态组件(FSC) ,但是 React Hooks 的出现,使得这些函数组件可以使用状态。因此,现在许多人将它们视为功能组件。

在这篇文章中,我会解释这些 Hooks 背后的动机,React 会发生什么改变,为什么我们不应该恐慌,以及如何在函数式组件中使用常见的 React Hooks,比如 state生命周期

译注:长文预警 🤓

为什么使用 React Hooks

React Hooks 是由 React 团队发明的,用于在函数组件中引入状态管理生命周期方法。如果我们希望一个 React 函数组件可以拥有 状态管理生命周期方法,我不需要再去将一个 React 函数组件重构成一个 React 类组件。React Hooks 让我们可以仅使用函数组件就可以完成一个 React 应用

不必要的组件重构

以前,只有 React 类组件可以使用 本地状态管理生命周期方法 。后者对于在 React 类组件中引入副作用(如监听DOM事件,异步加载数据)至关重要。

import React from 'react';

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

export default Counter;

只有在您不需要状态生命周期方法时,才会考虑使用 React 无状态组件(FSC)。而且因为 React 函数组件更轻便(更优雅),人们已经使用了大量的函数组件。每次,当这些 函数组件 需要状态生命周期方法时,都需要将 React 函数组件升级成 React 类组件(反之亦然)。

import React, { useState } from 'react';

// how to use the state hook in a React function component
function Counter() {
  const [count, setCount] = useState(0);

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

export default Counter;

有了 Hooks ,就没有必要进行这种重构。状态生命周期方法React 函数组件中变得可用。这也让 无状态组件功能组件 的重塑变得切实可行。

副作用逻辑

React 类组件中,副作用主要在生命周期方法中引入 (例如 componentDidMountcomponentDidUpdatecomponentWillUnmount) 。副作用可能是React 中获取数据,或 Browser API 进行交互 。通常这些副作用会伴随着设置和清理阶段。例如,如果您忘记了去删除监听器,就会遇到一些 React 性能问题

// side-effects in a React class component
class MyComponent extends Component {
  // setup phase
  componentDidMount() {
    // add listener for feature 1
    // add listener for feature 2
  }

  // clean up phase
  componentWillUnmount() {
    // remove listener for feature 1
    // remove listener for feature 2
  }

  ...
}

// side-effects in React function component with React Hooks
function MyComponent() {
  useEffect(() => {
    // add listener for feature 1 (setup)
    // return function to remove listener for feature 1 (clean up)
  });

  useEffect(() => {
    // add listener for feature 2 (setup)
    // return function to remove listener for feature 2 (clean up)
  });

  ...
}

现在,如果您在 React 类组件的生命周期方法中引入多个副作用,所有副作用将按生命周期方法分组,而不会按副作用的功能来分组。这就是 React Hooks 带来的改变,将每个副作用通过一个钩子函数进行封装,而每个钩子函数都会处理各自的副作用,并提供这个副作用的设置和清理。稍后您将在本篇教程中看到如何在 React Hook 中添加和删除监听器来实现这一点。

React 抽象地狱

React 中可以通过 高阶组件Render Props 来实现抽象和可重用性。还有 React Context及其Provider和消费者组件 所提供的另一个层次的抽象。React 中的所有这些高级模式都使用了所谓的包装组件。对于正在创建更大的 React 应用程序的开发人员来说,以下组件的实现应该并不陌生。

import { compose } from 'recompose';
import { withRouter } from 'react-router-dom';

function App({ history, state, dispatch }) {
  return (
    <ThemeContext.Consumer>
      {theme =>
        <Content theme={theme}>
          ...
        </Content>
      }
    </ThemeContext.Consumer>
  );
}

export default compose(
  withRouter,
  withReducer(reducer, initialState)
)(App);

Sophie Alpert 将之称为 React 中的 “包装地狱” 。您不仅可以在组件实现时看到它,还可以在浏览器中检查组件时看到它。由于使用 Render Props 组件(包括 React's Context 提供的消费者组件)和高阶组件,很容易产生了几十个包装组件。由于所有抽象逻辑都被其他 React 组件所隐藏,我们的应用变成了一棵没有可读性的组件树🌲。而那些可见的组件也很难在浏览器的 DOM 中进行跟踪。那么,如果这些抽象的逻辑在函数组件中被一些封装好的副作用所代替,这些额外的组件将不再需要。

function App() {
  const theme = useTheme();
  const history = useRouter();
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <Content theme={theme}>
      ...
    </Content>
  );
}

export default App;

这就是 React Hooks 的魅力所在。所有副作用都可以直接在组件中使用,业务组件为了使用这些副作用,也不需要再引入其他组件所为容器。容器组件消失,逻辑只存在于作为函数的 React Hooks 中。

Andrew Clark 已经在他的鹅妹子嘤高阶组件库:recompose 中发表了一篇关于赞成 React Hooks声明

混乱的 JavaScript

JavaScript 很好地混合了两个世界:面向对象编程(OOP)函数式编程(FP)React 将许多的开发者带到了这两个世界。一方面,React (和 Redux )向人们介绍了 函数编程(FP) 的功能组合,一些函数式编程的通用编程概念(例如高阶函数,JavaScript 内置方法,如 mapreducefilter ),以及一些函数式编程的术语,如 不变性副作用React 本身没有真正介绍这些东西,因为它们是语言或编程范式的特性,但它们在 React 中被大量使用,这使得每个 React 开发人员都会自动成为一个更好的 JavaScript 开发人员

另一方面,在 React 中,可以使用 JavaScript 类作为定义 React 组件的一种方法。类只是声明,而组件的实际用法是它的实例。它会创建一个类实例,而类实例的 this 对象可以用于调用类的方法(例如 setStateforceUpdate ,以及其他自定义类方法)。但是,对于不是来自 OOP 背景的 React 初学者来说,学习曲线会更陡峭。这就是为什么类绑定,this 对象和继承可能令人困惑的原因。对于初学者来说,这一直是 React 最令人困惑的事情,我的 React书 中也只有几章在讲这方面的知识。

// I THOUGHT WE ARE USING A CLASS. WHY IS IT EXTENDING FROM SOMETHING?
class Counter extends Component {
  // WAIT ... THIS WORKS???
  state = { value: 0 };

  // I THOUGH IT'S THIS WAY, BUT WHY DO I NEED PROPS HERE?
  // constructor(props) {
  //  SUPER???
  //  super(props);
  //
  //  this.state = {
  //    value: 0,
  //  };
  // }

  // WHY DO I HAVE TO USE AN ARROW FUNCTION???
  onIncrement = () => {
    this.setState(state => ({
      value: state.value + 1
    }));
  };

  // SHOULDN'T IT BE this.onDecrement = this.onDecrement.bind(this); in the constructor???
  // WHAT'S this.onDecrement = this.onDecrement.bind(this); DOING ANYWAY?
  onDecrement = () => {
    this.setState(state => ({
      value: state.value - 1
    }));
  };

  render() {
    return (
      <div>
        {this.state.value}

        {/* WHY IS EVERYTHING AVAILABLE ON "THIS"??? */}
        <button onClick={this.onIncrement}>+</button>
        <button onClick={this.onDecrement}>-</button>
      </div>
    )
  }
}

现在,有许多人在争论 React 不应该移除 JavaScript classes, 尽管他们不理解这些类的概念。毕竟,这些概念都来自于语言自身。但是,引入 Hooks API 的假设之一就是让初学者在第一次编写 React 应用时可以不用使用类组件,从而提供更加平滑的学习曲线。

React Hooks 会发生什么变化?

每次引入新功能时,人们都会关注它。一些人对这一变化感到欣喜若狂,而另外一些人对这一变化感到担忧。我听说 React Hooks 最常见的问题是:

  • 一切都变了!莫名的感到恐慌......
  • ReactAngular 一样变得臃肿!
  • Hooks 没有用,classes 很好
  • 这是魔法!

让我在这里解决这些问题:

一切都在变化?

React Hooks 将改变我们将来编写 React 应用程序的方式。但是,目前没有任何变化。您仍然可以使用本地状态和生命周期方法编写类组件,并部署高级模式,例如高阶组件或 Render Props 组件。没有人会把这些知识从你身边带走。了解我如何将所有开源项目从旧版本升级到 React 16.6 。这些项目都没有问题。他们正在使用 HOCRender Props ,甚至是较为古老的 context API(如果我错了,请纠正我)。这些年来我所学到的一切仍然有效。React 团队确保 React 保持向后兼容。它与 React 16.7 保持相同。

react

ReactAngular 一样变得臃肿

React 作为一个第三方库,其 API 总是会给人一种短小精干的感觉 。现在是这样,将来也是如此。但是,考虑到几年前基于组件所构建的应用,在升级的时候不被其他更加先进的库所取代, React 引入了有利于旧 API 的变更。如果 React 是9012年发布的,也许他的功能只会保留函数组件和 Hooks。但 React 几年前就已经发布,它在发布新的特性的同时,还需要兼容之前的版本。可能会在几年弃用类组件和其生命周期方法,转而使用 React 函数组件和 Hooks,但目前,React 团队仍将 React 类组件保留在了他们的工具库中。毕竟, React 团队希望利用新的特性 Hooks 来陪伴 React 赢得一场马拉松,而不是一场短跑。显然,React HooksReact 添加了另一个 API,但它有利于在未来简化 React 的新 API。我喜欢这种转变,而不是拥有 "React 2",让一切都不同。

译注:这是在吐槽 Angular 嘛?😂

Hooks 没有用,classes 很好 ?

想象一下,你将从零开始学习 React ,你会被介绍给 React with Hooks 。也许 create-react-app 不会以 React 类组件开始,而是使用 React 函数组件。您需要了解组件的所有内容都是 React Hooks 。它们可以管理状态和副作用,因此您只需要知道 state hookeffect hook。这是 React 类组件之前为包含的一切。React 初学者学习 React 会更简单,而不需要 JavaScript 类(继承,thisbindingssuper,...)带来的所有其他开销。想象一下 React Hooks 作为一种编写 React 组件的新方法 - 这是一种全新的思维方式。我自己是一个持怀疑态度的人,但是一旦我用 React Hooks 写了几个更简单的场景,我确信这是最简单的 React 写作方式,同样也学习 React 最简单的方式。作为一个做了很多 React workshops 的人,我认为它消除了那些令 React 初学者所沮丧的 classes

这是魔法?

众所周知,React 是用 JavaScript 来实现的。当有人问我:“我为什么要学习 React ?”时,最好的解释就是:编写 React 应用程序会让你成为一个更好的 JavaScript 开发人员 。无论未来是否会使用新的库,每个人都可以在使用 React 的过程中,磨练他们的 JavaScript 技能和一些通用的编程技巧。这是 Redux 经常做的事情,同样也是 React 做的事情:流行,但是没有魔力,它只是普通的 JavaScript 。现在 React Hooks 出现了,在以前经常使用的纯函数组件中引入了一些有状态的东西,同时还引入了一些不容易让人接受的规则,这使得很多人都不明白底层发生了什么。但是这样考虑一下:在React 中的一个函数组件不仅仅是一个函数。您仍然必须将 React 作为库导入到源代码文件中。它对你的函数做了一些事情,使得函数在 React 的世界中变成了函数组件。此函数组件的实现始终是隐式的。如何在将 React Hooks 引入之前使用函数实现的函数组件呢?人们也接受了它,即使它有点像是一个魔法。现在,唯一改变的东西(也许它之前已经是这样)是这些函数组件带有一个额外的隐藏对象,可以跟踪的 Hooks。引用 Dan Abramov他的关于 Hooks 文章的话:“也许你想知道 React 在哪里为 Hooks 保持状态。答案是它保存在 React 为类保持状态的完全相同的位置。无论你如何定义你的组件,React 都会在内部维护一个更新队列”

最后,试一下这种思考方式

基于组件的解决方案(如 AngularVueReact)正在推动每个版本的 Web 开发的界限。它们建立在二十多年前发明的技术之上。他们的出现是为了让2018年的开发者更加轻松,而不是1998。他们疯狂地优化自身以满足现在和现在的需求。我们正在使用组件而不是 HTML模板 构建 Web 应用程序。虽然还没有实现,但我想象一个未来,我们坐在一起,为浏览器发明一个基于组件的标准。AngularVueReact 只是这一运动的先锋。

在下文中,我想通过示例深入介绍一些受欢迎的 React Hooks ,以帮助您加速了解这一变化。所有示例都可以在此GitHub存储库中找到。

React useState Hook

上文中,您已经在一个典型的计数器示例的代码片段中看到过 useState Hook。它用于管理函数组件中的本地状态。让我们在一个更详细的例子中使用 Hook,我们将管理一系列项目。在我的另一篇文章中,您可以了解有关在React中将数组作为状态进行管理的更多信息,但这次我们使用 React Hook 进行操作。让我们开始吧:

import React, { useState } from 'react';

const INITIAL_LIST = [
  {
    id: '0',
    title: 'React with RxJS for State Management Tutorial',
    url:
      'https://www.robinwieruch.de/react-rxjs-state-management-tutorial/',
  },
  {
    id: '1',
    title: 'A complete React with Apollo and GraphQL Tutorial',
    url: 'https://www.robinwieruch.de/react-graphql-apollo-tutorial',
  },
];

function App() {
  const [list, setList] = useState(INITIAL_LIST);

  return (
    <ul>
      {list.map(item => (
        <li key={item.id}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}

export default App;

useState Hook 接受一个初始状态作为参数,并通过使用数组解构返回两个可以命名的变量,您可以为它们取需要的名字。第一个变量是实际状态,而第二个变量是一个可以设置并返回新状态的函数。

这个示例的下一步是从列表中删除子元素。为了实现它,我们为列表中的每个子元素都设置了一个可单击按钮。单击处理程序会使用一个内联函数实现,因为稍后会在内联函数中使用 list 以及 setList 。 因此,您不需要将这些变量传递给处理程序,因为它们已经可以从组件的外部作用域中获得。

function App() {
  const [list, setList] = useState(INITIAL_LIST);

  function onRemoveItem() {
    // remove item from "list"
    // set the new list in state with "setList"
  }

  return (
    <ul>
      {list.map(item => (
        <li key={item.id}>
          <a href={item.url}>{item.title}</a>
          <button type="button" onClick={onRemoveItem}>
            Remove
          </button>
        </li>
      ))}
    </ul>
  );
}

因为一些原因,我们需要从列表中删除的子元素。使用高阶函数,我们可以将子元素的标识符传递给处理函数。否则,我们将无法从列表中删除的这些子元素。

function App() {
  const [list, setList] = useState(INITIAL_LIST);

  function onRemoveItem(id) {
    // remove item from "list"
    // set the new list in state with "setList"
  }

  return (
    <ul>
      {list.map(item => (
        <li key={item.id}>
          <a href={item.url}>{item.title}</a>
          <button type="button" onClick={() => onRemoveItem(item.id)}>
            Remove
          </button>
        </li>
      ))}
    </ul>
  );
}

最后,使用数组的内置方法过滤列表,删除包含标识符的子元素。它返回一个新列表,用于设置列表的新状态。

function App() {
  const [list, setList] = useState(INITIAL_LIST);

  function onRemoveItem(id) {
    const newList = list.filter(item => item.id !== id);
    setList(newList);
  }

  return (
    <ul>
      {list.map(item => (
        <li key={item.id}>
          <a href={item.url}>{item.title}</a>
          <button type="button" onClick={() => onRemoveItem(item.id)}>
            Remove
          </button>
        </li>
      ))}
    </ul>
  );
}

此时应该已经完成了。您可以根据传递给处理程序的标识符从列表中删除子元素。然后,处理函数过滤列表并使用 setList 函数设置列表的新状态。

useState Hook 为您提供了在函数组件中管理状态所需的一切:初始状态,最新状态和状态更新功能。其他一切都是 JavaScript 。此外,您不需要像以前一样在类组件中使用浅合并来保持 state 的更新。相反,您使用 useState 封装一个域(例如列表),但如果您需要另一个状态(例如计数器),则只需使用另一个 useState 封装此域。您可以在 React 的文档中阅读有关 useState Hook 的更多信息

React useEffect Hook

让我们转到下一个名为 useEffectHook。如前所述,功能组件应该能够使用 Hook 管理状态和副作用。上面我们已经使用 useState Hook 展示了管理状态。现在将 useEffect Hook 用于副作用,这些副作用通常用于与 Browser / DOM API 或外部 API(如数据获取)的交互。让我们看一下如何通过实现一个简单的秒表,将 useEffect HookBrowser API 相结合。您可以在GitHub 仓库中查看使用 React 类组件需要如何完成。

import React, { useState } from 'react';

function App() {
  const [isOn, setIsOn] = useState(false);

  return (
    <div>
      {!isOn && (
        <button type="button" onClick={() => setIsOn(true)}>
          Start
        </button>
      )}

      {isOn && (
        <button type="button" onClick={() => setIsOn(false)}>
          Stop
        </button>
      )}
    </div>
  );
}

export default App;

现在还没有秒表。但现在至少有条件渲染显示 “开始”“停止” 按钮。并且由 useState hook 来管理布尔标志的状态。

接下来让我们加入副作用:用 useEffect 来注册一个 interval 定时器。定时器函数每秒会向您的浏览器控制台发出一条记录。

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

function App() {
  const [isOn, setIsOn] = useState(false);

  useEffect(() => {
    setInterval(() => console.log('tick'), 1000);
  });

  return (
    <div>
      {!isOn && (
        <button type="button" onClick={() => setIsOn(true)}>
          Start
        </button>
      )}

      {isOn && (
        <button type="button" onClick={() => setIsOn(false)}>
          Stop
        </button>
      )}
    </div>
  );
}

export default App;

为了在组件卸载的时候移除定时器(以及每次渲染更新之后),您可以在 useEffect 中返回一个函数,以便清理任何内容。对于上面的例子,当组件不再存在时,不应该留下任何内存泄漏。

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

function App() {
  const [isOn, setIsOn] = useState(false);

  useEffect(() => {
    const interval = setInterval(() => console.log('tick'), 1000);

    return () => clearInterval(interval);
  });

  ...
}

export default App;

现在,您需要在安装组件时设置副作用,并在卸载组件时清除副作用。如果您需要记录函数的调用次数,您会看到每次组件状态发生变化时它都会设置一个新的定时器(例如,单击“开始”/“停止”按钮)。

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

function App() {
  const [isOn, setIsOn] = useState(false);

  useEffect(() => {
    console.log('effect runs');
    const interval = setInterval(() => console.log('tick'), 1000);

    return () => clearInterval(interval);
  });

  ...
}

export default App;

为了仅在组件的 mountunmount 时响应 ,可以将空数组作为第二个参数传递给它。

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

function App() {
  const [isOn, setIsOn] = useState(false);

  useEffect(() => {
    const interval = setInterval(() => console.log('tick'), 1000);

    return () => clearInterval(interval);
  }, []);

  ...
}

export default App;

但是,由于在每次渲染之后都会清除定时器,我们也需要在我们的业务周期中设置定时器。我们也可以告诉 effect 仅在 isOn 变量发生变化时运行。仅当数组中的一个变量发生更改时,effect 才会在更新周期中运行。如果将数组保持为空, effect 将仅在 mountunmount 时运行,因为没有要检查的变量是否再次运行副作用。

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

function App() {
  const [isOn, setIsOn] = useState(false);

  useEffect(() => {
    const interval = setInterval(() => console.log('tick'), 1000);

    return () => clearInterval(interval);
  }, [isOn]);

  ...
}

export default App;

现在,无论 isOn 布尔值是 true 还是 false ,定时器都在运行。接下来,我们希望定时器仅在秒表启动的时候运行。

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

function App() {
  const [isOn, setIsOn] = useState(false);

  useEffect(() => {
    let interval;

    if (isOn) {
      interval = setInterval(() => console.log('tick'), 1000);
    }

    return () => clearInterval(interval);
  }, [isOn]);

  ...
}

export default App;

现在在功能组件中引入另一个状态来跟踪秒表的计时器。它用于更新计时器,但仅在秒表被激活时使用。

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

function App() {
  const [isOn, setIsOn] = useState(false);
  const [timer, setTimer] = useState(0);

  useEffect(() => {
    let interval;

    if (isOn) {
      interval = setInterval(
        () => setTimer(timer + 1),
        1000,
      );
    }

    return () => clearInterval(interval);
  }, [isOn]);

  return (
    <div>
      {timer}

      {!isOn && (
        <button type="button" onClick={() => setIsOn(true)}>
          Start
        </button>
      )}

      {isOn && (
        <button type="button" onClick={() => setIsOn(false)}>
          Stop
        </button>
      )}
    </div>
  );
}

export default App;

代码中仍然存在一个错误。当定时器运行时,它会将 timer 每秒加 1 。但是,它始终依赖于计时器的初始状态在累加。只有当 inOn 布尔标志改变时,状态才会正常显示。为了在计时器在运行时,始终接收最新的状态,您可以使用函数代替代替状态,是的每次更新的时候都可以拿到最新的状态。

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

function App() {
  const [isOn, setIsOn] = useState(false);
  const [timer, setTimer] = useState(0);

  useEffect(() => {
    let interval;

    if (isOn) {
      interval = setInterval(
        () => setTimer(timer => timer + 1),
        1000,
      );
    }

    return () => clearInterval(interval);
  }, [isOn]);

  ...
}

export default App;

另一种方法是,在计时器改变时,运行 effect。然后 effect 将收到最新的计时器状态。

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

function App() {
  const [isOn, setIsOn] = useState(false);
  const [timer, setTimer] = useState(0);

  useEffect(() => {
    let interval;

    if (isOn) {
      interval = setInterval(
        () => setTimer(timer + 1),
        1000,
      );
    }

    return () => clearInterval(interval);
  }, [isOn, timer]);

  ...
}

export default App;

这是使用浏览器 API 实现的秒表效果,如果您想继续,您也可以通过提供 “重置” 按钮来扩展示例。

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

function App() {
  const [isOn, setIsOn] = useState(false);
  const [timer, setTimer] = useState(0);

  useEffect(() => {
    let interval;

    if (isOn) {
      interval = setInterval(
        () => setTimer(timer => timer + 1),
        1000,
      );
    }

    return () => clearInterval(interval);
  }, [isOn]);

  const onReset = () => {
    setIsOn(false);
    setTimer(0);
  };

  return (
    <div>
      {timer}

      {!isOn && (
        <button type="button" onClick={() => setIsOn(true)}>
          Start
        </button>
      )}

      {isOn && (
        <button type="button" onClick={() => setIsOn(false)}>
          Stop
        </button>
      )}

      <button type="button" disabled={timer === 0} onClick={onReset}>
        Reset
      </button>
    </div>
  );
}

export default App;

That’s it. 使用 useEffect hook 来管理 React 函数组件的副作用,比如 Browser / DOM API 或其他第三方 API 的交互(例如数据获取)。您可以在 React 官方文档 useEffect hook 部分 获取更多信息。

自定义 React Hooks

最后同样重要的是,在您了解了两个最常用的在函数组件中引入状态和副作用的 Hooks 之后,我还想告诉您最后一件事:自定义 Hooks 。没错,您可以实现自己的自定义 React Hooks,在您的应用程序中或提供给其他人中使用。让我们看看接下来的示例:一个能够检测您的设备是在线还是离线的应用程序是如何工作的。

import React, { useState } from 'react';

function App() {
  const [isOffline, setIsOffline] = useState(false);

  if (isOffline) {
    return <div>Sorry, you are offline ...</div>;
  }

  return <div>You are online!</div>;
}

export default App;

接下来,为副作用引入 useEffect hook。在这种情况下,effect 会添加和删除检查设备是联机还是脱机的监听器。两个监听器在 mount 时只设置一次,在 unmount 时清理一次(空数组作为第二个参数)。每当调用其中一个监听器时,它就会设置 isOffline 布尔值的状态。

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

function App() {
  const [isOffline, setIsOffline] = useState(false);

  function onOffline() {
    setIsOffline(true);
  }

  function onOnline() {
    setIsOffline(false);
  }

  useEffect(() => {
    window.addEventListener('offline', onOffline);
    window.addEventListener('online', onOnline);

    return () => {
      window.removeEventListener('offline', onOffline);
      window.removeEventListener('online', onOnline);
    };
  }, []);

  if (isOffline) {
    return <div>Sorry, you are offline ...</div>;
  }

  return <div>You are online!</div>;
}

export default App;

现在,让我们将这些功能封装在一个 Effect 中。这是一个很棒的功能,我们希望其他地方也可以重用。这就是为什么我们可以提取功能,作为一个自定义 Hook, 它遵循与其他 Hook 相同的命名约定。

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

function useOffline() {
  const [isOffline, setIsOffline] = useState(false);

  function onOffline() {
    setIsOffline(true);
  }

  function onOnline() {
    setIsOffline(false);
  }

  useEffect(() => {
    window.addEventListener('offline', onOffline);
    window.addEventListener('online', onOnline);

    return () => {
      window.removeEventListener('offline', onOffline);
      window.removeEventListener('online', onOnline);
    };
  }, []);

  return isOffline;
}

function App() {
  const isOffline = useOffline();

  if (isOffline) {
    return <div>Sorry, you are offline ...</div>;
  }

  return <div>You are online!</div>;
}

export default App;

将自定义 Hook 作为函数提取出来并不是唯一的事情。您还必须将 isOffline 状态从自定义 Hook 中返回,以便在应用程序中向用户显示是否离线的消息。否则,它应该呈现正常的应用程序。这就是自定义 Hook ,它可以检测您是在线还是离线。您可以在 React 的文档中阅读有关自定义 Hook 的更多信息。

可重用的 React Hooks 最棒的地方,是它有可能发展自定义 React Hooks 的生态系统,我们可以从 npm 为任何 React 应用程序安装 Hooks。而且不仅仅是 React 应用程序。Vue的作者 Evan You (尤雨溪) 也被 Hooks 迷住了!。也许我们会看到两个生态系统之间的桥梁,可以在 VueReact 之间共享 Hooks


如果您想深入了解 state hookeffect hook ,可以查看以下 React Hooks 教程:

查看 React 文档中关于钩子的官方FAQ规则,以了解有关其细粒度行为的更多信息。此外,您还可以查看所有官方提供的React Hooks

对于仍然关注 React Hooks 的每个人:给自己一个机会。使用状态和副作用实现几个 React 函数组件。我们必须自己去理解它们是如何工作的,感受这种编程体验的升级。我必须说使用它们感觉非常棒。

译注:给我一个机会,重学 React ~
本文已经联系原文作者,并授权翻译,转载请保留原文链接

如果觉得我的文章对你有用,请随意赞赏

你可能感兴趣的

载入中...