[译] React Hooks 教程

原文地址: https://medium.com/@dtkatz/react-hooks-tutorial-learn-by-building-b90ec4db2b8e

原文作者: David Katz

Hooks

Hooks 是 React 即将推出的一个新功能。这一更新在 React 社区引起了巨大的反响。

简而言之,现在可以在 React 函数组件中使用 state 和其他 React 特性。因此没有必要使用 Class组件。

在这篇文章

我会教你使用 React Hooks 构建一个 Web 应用。对于一些代码,我也会列出没有使用 Hooks 的方式的代码。这样的话可以比较与使用 Hooks的区别。

如果你是一个 React 新手, Hooks是另一个值得学习的基础概念。

如果你是一个有经验的 React 开发者,没有必要忘记之前所掌握的 React 知识。事实上, Hooks 能够让你更好的写出简洁的 React 代码。

构建 App

在这个 Hooks 教程中,会构建一个叫 ‘Type Race’ 的 Web 应用。这个小游戏可以检测你输入一段文本的速度。玩这个游戏能让你上瘾,因为它提高了你打字的速度和使用键盘的灵活性!

例子: https://davidtkatz.com/#/typerace

教程源码: https://github.com/15Dkatz/typerace-hooks

构建项目

使用 npx 创建一个 React Hooks 例子项目,它可以直接使用 create-react-app 工具:

npx create-react-app typerace-hooks

进入到 typerace-hooks 目录,然后安装 react@16.8.0-alpha.1react-dom@16.8.0-alpha.1 。这个版本包含了 Hooks 的功能:

yarn add react@16.8.0-alpha.1 react-dom@16.8.0-alpha.1

确保 typerace-hooks/package.json 文件中依赖的 reactreact-dom 版本是 16.8.0-alpha.1

"react": "@16.8.0-alpha.1",  "react-dom": "@16.8.0-alpha.1"

Hook State

学习 Hooks 的最大部分是一种新的申明 state 的方式。因此创建这个app 的第一部分,就是新建一个组件,组件里的 state 是用 Hooks 申明的。

在 app 的中间,Type Race 会呈现一个文本输入框。输入框会跟踪用户输入到组件 state 中的文本。为了能够运行起来,我们重构一下 app.js 的代码,缩减到展示一个应用的标题,和输入框跟踪输入状态:

import React from 'react';
const App = () => {
  return (
    

Type Race

) } export default App;

现在,我们来使用 Hooks 。我们将使用 state 跟踪用户的输入。我们将在 app.js 的顶部引入 useState :

import React, { useState } from 'react';

然后我们会在 App 方法内的前面使用 useState hook,创建一个函数式的 state ‘userText’:

const App = () => {
const [userText, setUserText] = useState('');

这里有三个部分关于 useState :

  1. useState 的参数就是 state 的初始值。因此 useState('') ,就是给 userText 设置了一个 空字符串的初始值。
  2. useState 方法返回一个数组包含两个值。第一个值就是当前组件 state 的变量。因此在 const [userText, setUserText] = useState('');userText 表示当前值。它在代码中当作一个常量使用。
  3. 第二个返回值是一个函数。这个函数更新和它配对的变量的值。因此 setUserText 更新 userText 的值。

在 App 组件中,创建一个 updateUserText 辅助类的方法, 用来将用户输入的值设置到 userText 变量中。

这个辅助方法调用 setUserText , 将用户输入传递给这个方法。 updateUserText 的参数 event , event 参数里可以取到用户输入的值:

const App = () => {
  const [userText, setUserText] = useState('');
  const updateUserText = event => {
   setUserText(event.target.value);
   console.log(‘current userText’, userText);
  }

我们把 updateUserText 作为输入框 onChange 事件的回调函数。onChange 事件会在用户输入的时候触发。回调函数的 event (包含 event.target.value ) 参数会传递给 updateUserText 。同时 userText 表示输入框的值 :

现在运行并查看这个应用。在工程的根目录中使用命令行执行 npm run start 。应用运行在 localhost:3000 上。如果一切顺利,应用就会展示 Type Race 标题和一个输入框。

现在 useState 运行起来了。

与 class 组件的 state 比较

我们来比较一下 state 在 函数组件中与 class 组件中的不同:

有几个方面需求强调:

  • 就代码行数来说,hooks 的函数组件更胜一筹。使用 hooks 的代码用了14行,而使用传统 class 组件的代码则用了16行。
  • class 组件使用 this 对象调用 this.state , this.setStatethis.updateUserText 。长久以来对,React 开发者一直都需要关心 this 是怎样绑定到组件上面的。使用 .bind() 来修复这个问题是有点费劲的。使用 class 的属性初始化语法可以解决绑定的问题。但是你必须得认识到这一点就是你的方法能够访问到 this.setState 方法是因为组件继承 React.Component 时附加上去的。

Hook 的由来

通过上面的学习,你可以认为 useState 是一个 React 函数式 state 的钩子。函数组件是将 React 的 state 附加到自己身上。

在之前 React 组件继承上,class 类组件继承了所有的 React 功能,现在 函数组件也能附加到自己里面。

完成 Type Race

让我们把这个应用做完。接下来允许用户可以选择不同的文本片段。我们会加入计时的尝试。总体来说,需要更多的 state 和 JSX 代码。

针对初次使用的人,我们添加选择要输入代码片段的功能。在现有的代码的前面设置 userText,我们会添加一组初始的文本片段和记录被选中文本片段的变量:

const SNIPPETS = [
  'Bears, beets, battlestar galactica',
  "What's Forrest Gump's password? 1Forrest1",
  'Where do programmers like to hangout? The Foo Bar'
];
const [snippet, setSnippet] = useState('');
const [userText, setUserText] = useState('');

接着新建一个辅助方法允许用户选择文本片段。在方法内部,调用 useState 返回的 setState 方法。将这些代码写在 return 前面:

const chooseSnippet = snippetIndex => () => {
  console.log('setSnippet', snippetIndex);
  setSnippet(SNIPPETS[snippetIndex]);
};
return (

注意这个双箭头语法,它会让 chooseSnippet 返回一个方法。例如, chooseSnippet(0) 返回一个方法,这个方法最后会调用 setSnippet(SNIPPETS[0]); 。这里是关键,它会更新展示的文本片段以及提供一个按钮允许用户设置文本片段:

Type Race


Snippet

{snippet}
{ SNIPPETS.map((SNIPPET, index) => ( )) }

几个关键的点:

setSnippet
 SNIPPET 

接下来添加一个 gameState ,用来跟踪下面一些变量:

victory
startTime
endTime

当用户选择好一个片段时, gameState 记录一个开始时间。因此,我们会用 setGameState 来设置值 gameState.startGame 。注意当我们使用 set 方法设置值的时候,必须提供一个完整的键值对象。因此用对象扩展符创建一个包含完整 gameState 的对象。所以用 new Date().getTime() 覆盖 startTime

setSnippet(SNIPPETS[snippetIndex]);
setGameState({ ...gameState, startTime: new Date().getTime() });

gameState 还包含 victoryendTime 。都需要在用户输入完整的文本片段时进行更新。 endTime 是 当前时间 new Date().getTime 减去 gameState.startTime

const updateUserText = event => {
  setUserText(event.target.value);
  if (event.target.value === snippet) {
    setGameState({
      ...gameState,
      victory: true,
      endTime: new Date().getTime() - gameState.startTime
    });
  }
}

通过这些更改,我们可以为 gameState.victory 的场景添加 JSX 代码:

回到应用中,获胜的结果应该是这样的:

好的,现在就完成了这个应用!

此刻,你可以自行添加一些喜欢的CSS和样式更改。

其他的 Hooks

除了 useState ,还有其他的 hooks 将会把 React 概念 带入到 函数式组件中。

useState 之后就是 useEffectuseEffect hooks 会在组件每次渲染之后触发一个反馈,这个反馈就是你提供的一个回调函数。在这个场景下,React 引擎会触发一个副作用在 DOM 更新完毕(换句话说,每次渲染后)。

组件通常会触发副作用,比如请求服务端数据,发起订阅或者手动更改 DOM (例如让页面元素获得焦点)。这些都是使用 useEffect 的好场景。

这个 Hooks 可以在函数组件中承担之前在 class 组件生命周期函数 ( componentDidMount 或者 componentWillUnmount 等) 的作用。

在 Type Race 中, useEffect 可以设置 document.title 在完成的例子中:

import React, { useState, useEffect } from 'react';
…
  useEffect(() => {
    if (gameState.victory) document.title = 'Victory!';
  });

  const updateUserText = event => {

现在如果你顺利完成输入,会发现浏览器选项卡的标题会变成 “Victory!” :

结论

现在你已经使用 React hooks 构建了一个应用,希望你能体会到为什么这次更新会让 React 社区沸腾。React 团队一直在致力于打造整洁的函数式组件。美中不足的是,如果你需要创建本地 state ,就必须使用 class 组件。

使用 React hooks,就会变得两全其美:函数式组件也可以使用 state。