Open luzuoquan opened 5 years ago
原文地址: https://medium.com/@dtkatz/react-hooks-tutorial-learn-by-building-b90ec4db2b8e 原文作者: David Katz
原文地址: https://medium.com/@dtkatz/react-hooks-tutorial-learn-by-building-b90ec4db2b8e
原文作者: David Katz
Hooks 是 React 即将推出的一个新功能。这一更新在 React 社区引起了巨大的反响。
简而言之,现在可以在 React 函数组件中使用 state 和其他 React 特性。因此没有必要使用 Class组件。
我会教你使用 React Hooks 构建一个 Web 应用。对于一些代码,我也会列出没有使用 Hooks 的方式的代码。这样的话可以比较与使用 Hooks的区别。
如果你是一个 React 新手, Hooks是另一个值得学习的基础概念。
如果你是一个有经验的 React 开发者,没有必要忘记之前所掌握的 React 知识。事实上, Hooks 能够让你更好的写出简洁的 React 代码。
在这个 Hooks 教程中,会构建一个叫 'Type Race' 的 Web 应用。这个小游戏可以检测你输入一段文本的速度。玩这个游戏能让你上瘾,因为它提高了你打字的速度和使用键盘的灵活性!
例子: https://davidtkatz.com/#/typerace 教程源码: https://github.com/15Dkatz/typerace-hooks
使用 npx 创建一个 React Hooks 例子项目,它可以直接使用 create-react-app 工具:
create-react-app
npx create-react-app typerace-hooks
进入到 typerace-hooks 目录,然后安装 code>react@16.8.0-alpha.1</code 和 code>react-dom@16.8.0-alpha.1</code 。这个版本包含了 Hooks 的功能:
yarn add react@16.8.0-alpha.1 react-dom@16.8.0-alpha.1
确保 typerace-hooks/package.json 文件中依赖的 react 和 react-dom 版本是 16.8.0-alpha.1 。
typerace-hooks/package.json
react
react-dom
16.8.0-alpha.1
"react": "@16.8.0-alpha.1", "react-dom": "@16.8.0-alpha.1"
学习 Hooks 的最大部分是一种新的申明 state 的方式。因此创建这个app 的第一部分,就是新建一个组件,组件里的 state 是用 Hooks 申明的。
在 app 的中间,Type Race 会呈现一个文本输入框。输入框会跟踪用户输入到组件 state 中的文本。为了能够运行起来,我们重构一下 app.js 的代码,缩减到展示一个应用的标题,和输入框跟踪输入状态:
import React from 'react'; const App = () => { return ( <div> <h2>Type Race</h2> <input /> </div> ) } export default App;
现在,我们来使用 Hooks 。我们将使用 state 跟踪用户的输入。我们将在 app.js 的顶部引入 useState :
useState
import React, { useState } from 'react';
然后我们会在 App 方法内的前面使用 useState hook,创建一个函数式的 state 'userText':
const App = () => { const [userText, setUserText] = useState('');
这里有三个部分关于 useState :
useState('')
const [userText, setUserText] = useState('');
userText
setUserText
在 App 组件中,创建一个 updateUserText 辅助类的方法, 用来将用户输入的值设置到 userText 变量中。
updateUserText
这个辅助方法调用 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 表示输入框的值 :
onChange
event.target.value
<input value={userText} onChange={updateUserText} />
现在运行并查看这个应用。在工程的根目录中使用命令行执行 npm run start 。应用运行在 localhost:3000 上。如果一切顺利,应用就会展示 Type Race 标题和一个输入框。
npm run start
现在 useState 运行起来了。
我们来比较一下 state 在 函数组件中与 class 组件中的不同:
有几个方面需求强调:
this
this.state
this.setState
this.updateUserText
.bind()
React.Component
通过上面的学习,你可以认为 useState 是一个 React 函数式 state 的钩子。函数组件是将 React 的 state 附加到自己身上。
在之前 React 组件继承上,class 类组件继承了所有的 React 功能,现在 函数组件也能附加到自己里面。
让我们把这个应用做完。接下来允许用户可以选择不同的文本片段。我们会加入计时的尝试。总体来说,需要更多的 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]); 。这里是关键,它会更新展示的文本片段以及提供一个按钮允许用户设置文本片段:
chooseSnippet(0)
setSnippet(SNIPPETS[0]);
<div> <h2>Type Race</h2> <hr /> <h3>Snippet</h3> {snippet} <input value={userText} onChange={updateUserText} /> <hr /> { SNIPPETS.map((SNIPPET, index) => ( <button onClick={chooseSnippet(index)} key={index}> {SNIPPET.substring(0, 10)}... </button> )) } </div>
几个关键的点:
setSnippet
SNIPPET
接下来添加一个 gameState ,用来跟踪下面一些变量:
gameState
victory
startTime
endTime
当用户选择好一个片段时, gameState 记录一个开始时间。因此,我们会用 setGameState 来设置值 gameState.startGame 。注意当我们使用 set 方法设置值的时候,必须提供一个完整的键值对象。因此用对象扩展符创建一个包含完整 gameState 的对象。所以用 new Date().getTime() 覆盖 startTime :
setGameState
gameState.startGame
new Date().getTime()
setSnippet(SNIPPETS[snippetIndex]); setGameState({ ...gameState, startTime: new Date().getTime() });
gameState 还包含 victory 和 endTime 。都需要在用户输入完整的文本片段时进行更新。 endTime 是 当前时间 new Date().getTime 减去 gameState.startTime :
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和样式更改。
除了 useState ,还有其他的 hooks 将会把 React 概念 带入到 函数式组件中。
在 useState 之后就是 useEffect 。useEffect hooks 会在组件每次渲染之后触发一个反馈,这个反馈就是你提供的一个回调函数。在这个场景下,React 引擎会触发一个副作用在 DOM 更新完毕(换句话说,每次渲染后)。
useEffect
组件通常会触发副作用,比如请求服务端数据,发起订阅或者手动更改 DOM (例如让页面元素获得焦点)。这些都是使用 useEffect 的好场景。
这个 Hooks 可以在函数组件中承担之前在 class 组件生命周期函数 ( componentDidMount 或者 componentWillUnmount 等) 的作用。
在 Type Race 中, useEffect 可以设置 document.title 在完成的例子中:
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。
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
工具:进入到 typerace-hooks 目录,然后安装 code>react@16.8.0-alpha.1</code 和 code>react-dom@16.8.0-alpha.1</code 。这个版本包含了 Hooks 的功能:
确保
typerace-hooks/package.json
文件中依赖的react
和react-dom
版本是16.8.0-alpha.1
。Hook State
学习 Hooks 的最大部分是一种新的申明 state 的方式。因此创建这个app 的第一部分,就是新建一个组件,组件里的 state 是用 Hooks 申明的。
在 app 的中间,Type Race 会呈现一个文本输入框。输入框会跟踪用户输入到组件 state 中的文本。为了能够运行起来,我们重构一下 app.js 的代码,缩减到展示一个应用的标题,和输入框跟踪输入状态:
现在,我们来使用 Hooks 。我们将使用 state 跟踪用户的输入。我们将在 app.js 的顶部引入
useState
:然后我们会在 App 方法内的前面使用 useState hook,创建一个函数式的 state 'userText':
这里有三个部分关于
useState
:useState('')
,就是给 userText 设置了一个 空字符串的初始值。const [userText, setUserText] = useState('');
,userText
表示当前值。它在代码中当作一个常量使用。setUserText
更新userText
的值。在 App 组件中,创建一个
updateUserText
辅助类的方法, 用来将用户输入的值设置到userText
变量中。这个辅助方法调用
setUserText
, 将用户输入传递给这个方法。updateUserText
的参数 event , event 参数里可以取到用户输入的值:我们把
updateUserText
作为输入框onChange
事件的回调函数。onChange 事件会在用户输入的时候触发。回调函数的 event (包含event.target.value
) 参数会传递给updateUserText
。同时 userText 表示输入框的值 :现在运行并查看这个应用。在工程的根目录中使用命令行执行
npm run start
。应用运行在 localhost:3000 上。如果一切顺利,应用就会展示 Type Race 标题和一个输入框。现在 useState 运行起来了。
与 class 组件的 state 比较
我们来比较一下 state 在 函数组件中与 class 组件中的不同:
有几个方面需求强调:
this
对象调用this.state
,this.setState
和this.updateUserText
。长久以来对,React 开发者一直都需要关心this
是怎样绑定到组件上面的。使用.bind()
来修复这个问题是有点费劲的。使用 class 的属性初始化语法可以解决绑定的问题。但是你必须得认识到这一点就是你的方法能够访问到this.setState
方法是因为组件继承React.Component
时附加上去的。Hook 的由来
通过上面的学习,你可以认为 useState 是一个 React 函数式 state 的钩子。函数组件是将 React 的 state 附加到自己身上。
在之前 React 组件继承上,class 类组件继承了所有的 React 功能,现在 函数组件也能附加到自己里面。
完成 Type Race
让我们把这个应用做完。接下来允许用户可以选择不同的文本片段。我们会加入计时的尝试。总体来说,需要更多的 state 和 JSX 代码。
针对初次使用的人,我们添加选择要输入代码片段的功能。在现有的代码的前面设置 userText,我们会添加一组初始的文本片段和记录被选中文本片段的变量:
接着新建一个辅助方法允许用户选择文本片段。在方法内部,调用 useState 返回的 setState 方法。将这些代码写在 return 前面:
注意这个双箭头语法,它会让 chooseSnippet 返回一个方法。例如,
chooseSnippet(0)
返回一个方法,这个方法最后会调用setSnippet(SNIPPETS[0]);
。这里是关键,它会更新展示的文本片段以及提供一个按钮允许用户设置文本片段:几个关键的点:
setSnippet
参数是 index 。为什么要这么设计?SNIPPET
内部的 Id 作为 key 。但在这篇学习中并没有必要这么做。接下来添加一个
gameState
,用来跟踪下面一些变量:victory
用户是否全部输完文本片段。startTime
用户开始的时刻从选择完要输入的片段起 。endTime
用户完成输入的时刻当用户选择好一个片段时,
gameState
记录一个开始时间。因此,我们会用setGameState
来设置值gameState.startGame
。注意当我们使用 set 方法设置值的时候,必须提供一个完整的键值对象。因此用对象扩展符创建一个包含完整gameState
的对象。所以用new Date().getTime()
覆盖startTime
:gameState
还包含victory
和endTime
。都需要在用户输入完整的文本片段时进行更新。endTime
是 当前时间new Date().getTime
减去gameState.startTime
:通过这些更改,我们可以为 gameState.victory 的场景添加 JSX 代码:
回到应用中,获胜的结果应该是这样的:
好的,现在就完成了这个应用!
此刻,你可以自行添加一些喜欢的CSS和样式更改。
其他的 Hooks
除了
useState
,还有其他的 hooks 将会把 React 概念 带入到 函数式组件中。在
useState
之后就是useEffect
。useEffect
hooks 会在组件每次渲染之后触发一个反馈,这个反馈就是你提供的一个回调函数。在这个场景下,React 引擎会触发一个副作用在 DOM 更新完毕(换句话说,每次渲染后)。组件通常会触发副作用,比如请求服务端数据,发起订阅或者手动更改 DOM (例如让页面元素获得焦点)。这些都是使用
useEffect
的好场景。这个 Hooks 可以在函数组件中承担之前在 class 组件生命周期函数 ( componentDidMount 或者 componentWillUnmount 等) 的作用。
在 Type Race 中,
useEffect
可以设置document.title
在完成的例子中:现在如果你顺利完成输入,会发现浏览器选项卡的标题会变成 "Victory!" :
结论
现在你已经使用 React hooks 构建了一个应用,希望你能体会到为什么这次更新会让 React 社区沸腾。React 团队一直在致力于打造整洁的函数式组件。美中不足的是,如果你需要创建本地 state ,就必须使用 class 组件。
使用 React hooks,就会变得两全其美:函数式组件也可以使用 state。