微信号:frontshow

介绍:InfoQ大前端技术社群:囊括前端、移动、Node全栈一线技术,紧跟业界发展步伐。

React将引入Hooks,你怎么看?

2018-10-26 18:57 前端之巅
译者|姚佳灵、无明
整理|覃云

今天,在 2018 ReactConf 大会上,React 官方宣布 React v16.7.0-alpha 将引入 Hooks,乍一看,你可能在想 Hooks 是什么?有什么用?且看下文分析。

Hooks 是什么?

Hooks 是一种函数,该函数允许你“勾住(hook into)”React 状态和来自函数组件的生命周期功能。Hook 在类内部不起作用,它们允许你无需类就使用 React。(不建议你马上开始重写你现有的组件,但你可以在新组件中开始使用 Hook。)

React 提供了一些内置 Hook,如 useState,你也可以创建自定义 Hooks 以在不同的组件中复用有状态行为。

根据 React 官方给出的文档,Hooks 主要分为以下几种:

  • State Hooks

  • Effect Hooks

  • 自定义 Hooks

State Hook

该示例显示了一个计数器,点击该按钮,值会递增。

import { 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 将在重新渲染之间保留这个状态。useState 返回一对值:currentstate 值和允许你更新它的函数。

可以从事件处理程序或其他位置调用该函数,这与类中的 this.setState 类似,除了其不会把旧的和新的状态合并在一起。

声明多个状态变量

可以在单个组件中多次使用 State Hook:

function ExampleWithManyStates() {
  // Declare multiple state variables!
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
  // ...
}

数组解构语法允许我们给状态变量取不同的名字,这些变量是我们通过调用 useState 声明的,这不是 useState API 的一部分。相反,React 假设如果多次调用 useState,那么在每次渲染的时候要遵循相同的顺序来做。

Effect Hook

你之前很可能已经执行了数据提取、订阅、或手工改变来自 React 组件的 DOM。我们称这些操作为“副作用(side effect)”,因为它们会影响其他组件,并且在渲染过程中无法完成。

Effect Hook、useEffect,增加了从函数组件执行副作用的功能。它与 React 类中的 componentDidMount、componentDidUpdate、和 componentWillUnmount 有相同的功能,但是统一为单个 API。(我们将展示一个例子,该示例在 Using the Effect Hook 中对 useEffect 和这些方法进行比较。)

例如,此组件在 React 更新 DOM 后设置文档标题:

import { 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 时,你通知 React 在刷新对 DOM 的更改后运行你的“effect”函数。Effect 在组件内声明,因此可以访问其 props 和 state。默认情况下,React 在每次渲染后运行 effect,包括第一次渲染。

效果还可以通过返回一个函数来指定它们之后如何“清理”。例如,此组件使用 effect 来订阅朋友的在线状态,并通过取消订阅来清理:

import { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);

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


  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

在此示例中,当组件卸载时,以及在由于后续渲染而重新运行 effect 之前,React 将取消订阅我们的 ChatAPI。(如果我们传递给 ChatAPI 的 props.friend.id 没有变化,有办法让 React 跳过重新订阅。)就像使用 useState 一样,可以在组件中使用多个 effect:

function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }
  // ...

Hook 允许你通过部件的关联情况(例如添加和删除订阅)来组织组件中的副作用,而不是基于生命周期方法强制拆分。

自定义 Hooks

自定义 Hooks 主要用来复用组件逻辑,详情请阅读:

https://reactjs.org/docs/hooks-overview.html

Hook 的规则

Hook 是 JavaScript 函数,但强加了两个额外的规则:

  • 只能在顶层调用 Hook,不要在循环、条件或嵌套函数中调用 Hook。

  • 仅从 React 功能组件调用 Hook。不要从常规 JavaScript 函数调用 Hook。(还有另一个有效的地方来调用 Hook,即你的自定义 Hook。)

这里(https://www.npmjs.com/package/eslint-plugin-react-hooks)提供了一个 linter 插件来自动执行这些规则,这些规则乍看起来有些令人疑惑,但它们对 Hook 的良好运行至关重要。

为什么要在 React 中引入 Hooks?

至于为何要在 React 中引入 Hooks,React 官方给出了答案:

Hooks 解决了 React 中各种看似不相关的问题,这些问题是我们在开发和维护数以万计的组件时遇到的。无论你是在学习 React,还是每天在使用它,还是选择使用具有类似组件模型的其他库,你都可能会发现这类问题。

难以在组件之间重用有状态逻辑

React 没有提供可将可重用行为“附加”到组件的方法。如果你用过 React 一段时间,可能会对渲染 prop 和高阶组件等模式比较熟悉,它们试图解决这个问题。但是这些模式要求你在使用它们时重构组件,这可能很麻烦,并且代码会变得难以维护。

如果你看一下 React DevTools 中的典型 React 应用程序,你可能会发现“包装器地狱”,组件被层层的提供者、消费者、高阶组件、渲染 prop 和其他抽象组件组包围。虽然我们可以在 DevTools 中过滤掉它们,但这反应了一个更深层次的问题:React 需要一个更好的原语来共享有状态逻辑。

使用 Hooks,你可以从组件中提取有状态逻辑,可以进行独立测试和重用。Hooks 允许你在不更改组件层次结构的情况下重用有状态逻辑。这样可以轻松地在多个组件之间或与社区共享 Hooks。

复杂的组件变得难以理解

我们通常维护的组件都先从简单的开始,然后逐渐加入无法管理的状态逻辑和副作用。每个生命周期方法通常包含不相关逻辑的混合。例如,组件可能会在 componentDidMount 和 componentDidUpdate 时获取数据。不过,相同的 componentDidMount 方法可能还包含一些设置事件监听器的无关逻辑,并在 componentWillUnmount 中执行清理工作。一起更改的相互关联的代码会被拆分,但完全不相关的代码最终会组合在一个方法中。这样就很容易引入错误和导致不一致。

在很多情况下,很难将这些组件分解为更小的组件,因为状态逻辑到处都是。测试它们也很困难。这是很多人更喜欢将 React 与单独的状态管理库相结合的原因之一。不过,这通常会引入太多的抽象,要求你在不同的文件之间跳转,让重用组件变得更加困难。

为了解决这个问题,Hooks 让你可以根据相关的部分将一个组件拆分为较小的函数,而不是基于生命周期方法进行强制拆分。你还可以选择使用 reducer 来管理组件的本地状态,以使其更具可预测性。

类让人和机器感到困惑

根据我们的观察,类是学习 React 最大的障碍。你必须了解 JavaScript 中的 this 是怎么回事,这与它在大多数语言中的使用方法有很大不同。你必须记住绑定事件处理程序。人们可以很好地理解 prop、状态和自上而下的数据流,但对类的使用仍然感到很挣扎。React 中的函数和类组件之间的区别以及何时使用哪个组件在经验丰富的 React 开发人员之间也存在分歧。

另外,React 已经推出了大约五年时间,Facebook 希望 React 在未来五年充满活力。正如 Svelte、Angular、Glimmer 和其他项目所表明的那样,预编译组件具有很大的潜力。

最近,他们一直在尝试使用 Prepack(https://prepack.io/)进行组件折叠,我们已经看到了充满希望的结果。不过,他们发现类组件的无意识模式可能会使这些优化回退。在类上应用现今的一些工具时也存在一些问题。例如,类难以进行 minify,并且类会导致热重载变得不可靠。他们希望提供一种 API,使代码更有可能保持在可优化的路径上。

为了解决这些问题,Hooks 让你可以在不使用类的情况下使用更多的 React 特性。从概念上讲,React 组件很接近于函数。Hooks 拥抱函数,但不会以牺牲 React 精神为代价。Hooks 提供了命令式的编程方式,不需要你学习复杂的函数式或反应式编程技术。

例子:https://reactjs.org/docs/hooks-overview.html

逐步采用策略

我们知道,React 开发人员专注于产品,他们没有时间研究发布的每个新的 API。Hooks 是新东西,在学习或采用它们之前先等待更多的示例和教程,这样可能会更好。

我们也知道为 React 添加新原语的标准是非常高的。对于好奇的读者,这里准备了一个详细的 RFC(https://github.com/reactjs/rfcs/pull/68),其中包含更多的细节,并为特定设计决策和现有相关技术提供了额外的视角。

关键的是,Hooks 可以与现有代码同时存在,因此你可以逐步采用它们。React 官方正在分享这个实验性的 API,以便从社区中那些有兴趣打造 React 未来的人那里获得早期反馈,将公开进行 Hooks 的迭代。

最后,没有必要急于使用 Hooks。建议避免任何“重大的重写”,特别是对于复杂的现有类组件来说。进入“Hooks 思考”模式需要精神上的转变。根据经验,最好先在非关键的新组件中练习使用 Hooks,并确保团队中的每个人都能适应。

React 官方打算让 Hooks 涵盖所有与类相关的用例,但在可预见的未来,他们将继续支持类组件。在 Facebook 有数万个类组件,他们表示绝对没有计划重写它们。相反,他们开始在新代码中使用 Hooks。

怎样使用 Hooks?

假设我们正在尝试编写一个 Counter 组件,正常的实现如下:

而基于 Hooks 的实现将如下:

使用 Hooks 的好处非常明显:

  • 减少代码实现;

  • 共享逻辑,可以将逻辑解耦。

参考链接

https://reactjs.org/docs/hooks-intro.html#motivation

https://reactjs.org/docs/hooks-overview.html

 活动推荐

今年12 月 7-8 日在北京国际会议中心举办的 ArchSummit 全球架构师技术峰会邀请了超过百位的国内外专业讲师,并设置了前端技术专题,分享他们的最新黑科技和研发经验。

目前大会 8 折优惠购票火热进行中,扫描以下图片二维码或点击“阅读原文”了解更多详情!

如有疑问欢迎咨询票务经理灰灰:17326843116(微信同号)


 
前端之巅 更多文章 gRPC-Web发布,REST又要被干掉了? Node.js 11 发布,Node 10正式进入LTS 你知道CSS实现水平垂直居中的第10种方式吗? 如何创建高性能、可扩展的Node.js应用? 基于Vue+Electron构建桌面应用程序实践
猜您喜欢 “自由即奴役”的Google AMP 微软收购GitHub:欲在云计算市场对抗AWS,这次微软能打破收购惨败的魔咒吗 远程调试你的 Python 代码 给Android 初学者的入门系列教程 老司机告诉你,如何同时运营更新150款App | 宝宝巴士CTO蔡泸炜