【译】什么是React Hooks

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

react

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

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

译注:长文预警 🤓

为何使用 React Hooks

React Hooks 是由 React 团队发明的,用于在函数组件中引入状态管理生命周期方法。若是咱们但愿一个 React 函数组件能够拥有 状态管理生命周期方法,我不须要再去将一个 React 函数组件重构成一个 React 类组件。React Hooks 让咱们能够仅使用函数组件就能够完成一个 React 应用java

没必要要的组件重构

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

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 类组件(反之亦然)。git

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 函数组件中变得可用。这也让 无状态组件功能组件 的重塑变得切实可行。github

反作用逻辑

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

// 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 应用程序的开发人员来讲,如下组件的实现应该并不陌生。api

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 ~ 本文已经联系原文做者,并受权翻译,转载请保留原文连接
相关文章
相关标签/搜索