Open WangShuXian6 opened 1 month ago
如果你关注过 React 库的发展,你可能听说过 React Hooks。
Hooks 是一些函数,我们可以用它们来处理状态、获取数据以及与浏览器 API 进行交互。
Hooks 还为我们提供了一种编写可以跨文件和项目共享的函数的方法,便于代码复用。
在过去的六年里,我几乎每天都在使用 React。
我非常喜欢使用 Hooks,因为它们功能强大、灵活,并且可以扩展到各种项目中。
由于像 Twitter、Netflix、Airbnb 和 PayPal 这样的巨型公司都在使用 React,
对 React 的深入理解可以带来更多的职业机会。
在本课程中,我们将探讨为什么要使用 Hooks 以及它们可以帮助你解决哪些问题。
这不仅仅是追随潮流,而是让我们的开发工作更加有趣。
所以,让我们来探索一些使用 Hooks 现代化我们的 React 代码的方法吧。
在开始之前,这里有一些关于课程的注意事项。
React 是一个 JavaScript 库,因此在开始这门课程之前,最好具备一定的 JavaScript 技能。
如果你想提升 JavaScript 技能,我推荐 Morten Rand Hendrickson 的《JavaScript 基础训练》。
此外,这是一门中级 React 课程,因此你最好熟悉创建组件、处理 props 和使用 NPM 安装包。
如果你需要复习 React 的基础知识,可以参加我的课程《Learning React.js》来了解基本概念。
你不需要太多准备就可以开始这门课程。我们只需要一个代码编辑器。
我将使用 Visual Studio Code,但我也会向你展示如何使用 Code Sandbox,这只需要一个浏览器。
这就是我们所需的一切。让我们开始吧!
[MUSIC]
如果你熟悉 React 的历史,你可能知道在不同的时期,创建组件的方法有所不同。
如果我们乘坐时光机回到 2013 到 2017 年之间(并不是很久以前),你会发现当时我们是使用一个叫做 createClass
的函数来创建组件的。
然后,createClass
函数中有一个 render
方法,我们可以用它来返回一些 UI。
到了 2017 年左右,createClass
开始不那么受欢迎了,因为 JavaScript 实现了类语法,这非常适合用于创建组件。
这意味着我们的组件开始变成了这个样子:
我们有了一个类,它会继承一个叫做 React.Component
的基类,整体上看起来与之前差不多。
不过在那个时候,使用函数来创建组件也是可能的。
但每次你添加状态变量时,你都必须将函数组件重构为类组件,这非常耗时。
然后 React Hooks 出现了。
Hooks 允许你在函数组件中添加状态,也允许你将逻辑抽象为独立的函数。它们其实就是一些函数。
每当你使用 React 或其他库中的 Hook 时,函数名都必须以 use
开头。
useState
、useReducer
、useQuery
、useContext
等等,所有 Hook 都是以 use
开头的。
往前看,最好使用函数而不是类,因为类语法在未来的某个时候可能会从 React 库中移除。
在下一个视频中,我们将生成一个 React 项目,以便我们可以实际测试这些 Hooks。
Create React App 是创建新 React 应用程序最常用的工具之一。
Create React App 为我们提供了一个 React 项目结构,支持最新的 JavaScript 特性,并提供优化应用的工具,方便生产环境使用。
要开始使用 Create React App,你需要做几件事:
输入 node -v
,查看当前的 Node 版本。只要你的版本高于 8.10
,你就可以使用 Create React App。
接下来检查 npm。npm 是 Node 的包管理器,我们要确保它的版本是 5
、6
或更高版本。
如果你的版本不符合要求,你可以前往 nodejs.org 下载最新版本。
确认有正确的版本后,进入一个用来运行项目的文件夹。使用 cd
(改变目录)切换到桌面,然后运行 Create React App,使用 npx
命令。
npx 是 npm 附带的一个包执行工具,它允许我们在不需要全局安装 Create React App 的情况下运行它。这非常方便。
我们将使用以下命令:
npx create-react-app React-Hooks
这个安装过程可能需要一点时间。
安装完成后,切换到 React Hooks 目录,然后运行以下命令:
npm start
npm start
会在 localhost:3000
启动我们的项目,这将自动在浏览器中打开并显示启动项目。
如果过程中出现问题,或者安装未成功,屏幕看起来与预期不同,或者你遇到了一堆错误,你可以打开一个新窗口,输入 react.new
。
react.new
会打开 Code Sandbox,这是一个基于浏览器的 IDE,你可以用它进行 React 开发。如果你更愿意在这样的环境下学习,你可以选择在这里进行操作。
你可以使用 GitHub 或 Google 账号登录。我会使用 GitHub 登录。
登录后,输入任何更改,比如 "Hello world"
,你将立即在输出中看到变化。这是快速开始 React 项目的一种非常方便的方式。
无论你是在浏览器中的 Code Sandbox 工作,还是在本地项目中工作,你现在已经准备好了。
在接下来的课程中,我们将学习如何创建一个组件。
在上一节视频中,我们学习了如何使用 Create React App 来生成一个 React 项目。
现在让我们稍微了解一下这个项目的结构。
在 package.json
文件中,我们可以看到包含了 React 以及一些测试依赖。
在 index.js
文件中,有一个 service worker。不过我们不需要它,所以我会将其删除,并移除所有与 service worker 相关的代码。
我还想指出一个最近版本的 React 变化,即 React 的严格模式(StrictMode
)。这是一个可以包裹组件的包装器。
使用 StrictMode
包裹组件的好处是它会给出一些警告,让你在错误发生之前修复潜在的 bug。
如果你违反了任何 React 规则,StrictMode
会让你知道。
接下来,让我们打开 app.js
文件。这是我们的应用组件,也是自动生成的主要组件。
我们先删除其中的所有内容。
然后添加一个 H1
标题,写上 "Hello world"。
<h1>Hello world</h1>
当我们完成这个操作后,它应该会立即渲染到浏览器中。
这就是我们所需的所有设置。
再次强调,我们的应用组件通过 ReactDOM.render
渲染到页面上。
所以现在我们已经具备了开始开发所需的一切。
在 React 中,使用对象解构最常见的地方是在组件内部。
这里我们有一个渲染 "Hello world" 的组件,但我们也可以传递一个名为 props
的对象到任意组件中。
这意味着我们可以将 "world" 替换为 props.name
。
然后我们回到 index.js
文件中,传递一个 name
属性,例如 "Alex"。
<App name="Alex" />
这样,组件将使用动态数据进行渲染。
props
解构我们可以通过对 props
对象进行解构来简化代码。因为 props
只是一个对象,我们可以使用解构来直接提取出我们需要的 name
。
const { name } = props;
保存后,尽管界面没有任何变化,但一切都按预期工作。
解构不仅仅可以用于对象,还可以用于数组,尤其是在 React Hooks 中大量使用。
在 index.js
文件中,我们创建一个包含一些人的数组:
const people = ['Alex', 'Allie', 'Anna'];
通常,如果我们想访问数组的第一个元素,我们会使用它的索引:
console.log(people[0]); // Alex
但通过数组解构,我们可以直接为数组中的每个元素指定变量名称。
const [first, second] = people;
console.log(first); // Alex
console.log(second); // Allie
如果你只想获取数组中的第三个元素,你可以跳过前两个元素:
const [, , third] = people;
console.log(third); // Anna
这种语法让我们可以为数组中的特定位置分配变量名称,非常简洁。
无论是对象还是数组解构,理解这两者将帮助你更高效地与现代 React 一起工作。
现在我们已经更好地了解了数组解构,让我们先移除这个数组。
我们还会在应用中移除 name
属性。
为了让一切显示在同一个屏幕上,我不会从 app.js
文件导入应用组件,而是直接在 index.js
中创建它。
在本视频中,我们将为应用组件添加状态。使用 React Hooks 来实现这一点需要调用一个函数。
首先,我们将返回一个 div
,并在其中添加一些内容,例如:
return <div>The package is</div>;
useState
Hook接下来,我们在第 6 行创建一个常量,并调用 useState
函数:
const [status, setStatus] = useState('not delivered');
为了使用 useState
,我们还需要从 React 库中导入它:
import { useState } from 'react';
useState
返回一个数组,其中的第一个元素是当前的状态值,第二个元素是一个函数,用于更新状态。
我们可以通过控制台输出结果来验证它的返回值:
console.log(status);
现在,我们已经定义了一个初始状态 "not delivered"。在 div
中显示该状态:
<div>The package is {status}</div>
除了显示状态,我们还需要一种方法来更改状态。我们将创建一个按钮,并在点击时更新状态:
<button onClick={() => setStatus('delivered')}>Deliver</button>
这样,每次点击按钮时,状态都会从初始状态 "not delivered" 更新为 "delivered"。
让我们点击按钮,查看状态是否变为 "delivered"。
这是 useState
工作原理的最简单示例。每次调用 useState
时,它会返回一个包含两个元素的数组:
通常,函数命名为 set
加上状态变量的名称,例如 setStatus
。虽然这不是强制要求,但这是常见的命名模式。
useState
结合输入元素另一种利用 useState
的方法是将其与输入元素结合使用。在这个例子中,我们将复选框的状态(选中或未选中)作为一个状态变量来管理。
首先,我们需要移除之前的代码,并清空之前的 useState
调用。
在 div
中添加一个输入元素,类型为 checkbox
:
<input type="checkbox" />
接着,创建一个状态变量 checked
和一个对应的状态更新函数 setChecked
:
const [checked, setChecked] = useState(false);
初始状态设置为 false
,表示复选框一开始是未选中的。
将 checked
变量绑定到输入框的值上:
<input type="checkbox" checked={checked} />
然后在旁边添加一个段落标签,用来显示复选框的状态:
<p>{checked ? "Checked" : "Not checked"}</p>
到目前为止,我们可以看到复选框的初始状态正确显示,但点击事件还没有生效。我们需要添加一个 onChange
事件处理函数来更新复选框的状态。
<input
type="checkbox"
checked={checked}
onChange={() => setChecked(!checked)}
/>
这里我们使用 setChecked(!checked)
来设置复选框的状态为 checked
的相反值,即点击时切换状态。
现在,复选框可以正确地切换状态,每次点击时都会在 "Checked" 和 "Not checked" 之间切换。
通过这个简单的例子,我们创建了一个复选框组件,并使用 useState
钩子来帮助我们跟踪复选框的状态。
在大多数情况下,React 应用程序不是由单一组件组成的。理解如何在组件树中管理状态是一个非常重要的概念。
在本节课程中,我们将构建一个星级评分组件,可以用于对书籍、电影、餐馆等进行评分。这种组件可以在不同类型的应用程序中复用。
StarRating
组件首先,App
组件只负责渲染另一个组件,我们将其命名为 StarRating
。
return <StarRating />;
Star
子组件StarRating
组件将使用一个更小的组件,名为 Star
,这个组件将构成所有的星级评分。
我们将从一个名为 React Icons
的独立库中导入星形图标。
npm install react-icons
React Icons
库现在我们导入 FAStar
(Font Awesome 的星形图标)作为星级评分的星形组件。
import { FaStar } from "react-icons/fa";
为了在页面上显示多个星星,我们需要创建一个辅助函数 createArray
,它根据给定长度返回一个数组。
const createArray = (length) => [...Array(length)];
接下来,我们定义 StarRating
组件,并使用 createArray
函数来渲染星星。我们将使用 map
来遍历数组并渲染每个星星。
const StarRating = ({ totalStars = 5 }) => {
return (
<>
{createArray(totalStars).map((n, i) => (
<Star key={i} />
))}
</>
);
};
在 App
组件中,我们可以传递一个 totalStars
属性来设置显示星星的数量。
<StarRating totalStars={10} />
如果没有传递 totalStars
,默认将显示 5 颗星。
接下来,我们为 Star
组件添加颜色属性。未来用户将能够点击星星并更改它们的颜色。
const Star = ({ selected = false }) => (
<FaStar color={selected ? "red" : "gray"} />
);
目前,所有星星都是灰色的。接下来,我们将实现点击时更改星星颜色的功能。
useState
进行状态管理在下一节课程中,我们将使用 useState
来实现点击星星时切换颜色的功能。
我们已经使用 React 的 props
创建了几个星星组件。接下来,我们需要处理点击星星的事件。为此,我们将使用 useState
来管理组件的状态。
我们首先需要在 StarRating
组件中创建一个状态变量,用于跟踪已选中的星星数:
const [selectedStars, setSelectedStars] = useState(0);
我们初始化 selectedStars
为 0
,表示刚开始没有任何星星被选中。
每个星星都是一个可点击的组件,因此我们需要为每个星星传递 selected
属性,使其能够访问状态变量。我们将根据星星的索引值来判断该星星是否被选中:
<Star
selected={selectedStars > i}
onSelect={() => setSelectedStars(i + 1)}
key={i}
/>
selected
属性将根据索引 i
来决定当前星星是否被选中,onSelect
用来更新状态,设置 selectedStars
为点击星星的索引加 1。
onSelect
函数通过传递的 setSelectedStars
方法来更新组件状态,并将点击的星星数更新为 i + 1
,确保正确捕获当前点击的星星:
const setStarRating = (index) => {
setSelectedStars(index + 1);
};
为了直观地显示当前选中的星星数量,我们可以在页面上显示一条信息,如 "已选星数/总星数"。我们通过添加一个段落标签来实现:
<p>
{selectedStars} of {totalStars} stars
</p>
每次点击星星时,应用状态会被更新,选中的星星数量将动态变化,显示效果如 "0/5", "3/5", "5/5" 等等。
我们使用 useState
管理星星的选中状态,并通过点击事件动态更新状态。每个星星组件都接收当前状态,并通过 onSelect
方法与 setSelectedStars
函数互动,更新界面上的选中状态。这样,我们就实现了一个完整的星级评分功能。
useEffect
处理副作用React 库提供了另一个钩子 useEffect
,它允许你在函数组件中处理副作用。
副作用可以是类似 console.log
的输出,或与某种 DOM API 交互,比如 history
或 window
。
首先,我们将清理代码,移除星级评分组件,并用一个简单的 section
替代:
<section>
<p>Congratulations</p>
</section>
为了使用 useEffect
,我们首先需要设置一个状态变量。我们将定义一个 name
变量,以及用于更新状态的 setName
函数,并设置初始状态为 "Jan":
const [name, setName] = useState("Jan");
然后在 section
内显示该状态:
<section>
<p>Congratulations {name}</p>
</section>
接着,我们添加一个按钮,用来改变赢家的名字:
<button onClick={() => setName("Will")}>Change Winner</button>
每次点击按钮时,名字将从 "Jan" 变为 "Will"。
useEffect
useEffect
允许我们在组件渲染后或状态更新后执行副作用。接下来,我们将使用 useEffect
来改变页面的标题。
useEffect(() => {
document.title = `Celebrate ${name}`;
}, [name]);
这个 useEffect
函数会在每次 name
状态改变时运行,并更新文档标题。例如,当页面加载时,标题会显示为 "Celebrate Jan",点击按钮后,标题会变为 "Celebrate Will"。
在本例中,我们使用了 useState
来管理名字状态,使用 useEffect
来处理副作用,即每次状态改变时更新页面标题。通过这种方式,我们可以轻松地在 React 中处理副作用。
在接下来的课程中,我们将学习如何自定义 useEffect
的触发时机。
useEffect
的执行时机我们在上一个例子中看到,每次状态变化时,useEffect
都会被调用。然而,有时我们不希望它在每次渲染或状态更新后都运行,这时我们可以通过依赖项数组来控制 useEffect
的执行时机。
当我们给 useEffect
传递一个空数组作为第二个参数时,useEffect
只会在组件首次渲染时运行一次,而不会在后续的状态更新时再次触发。
useEffect(() => {
console.log("Effect called");
}, []);
在这个例子中,useEffect
只会在组件首次加载时调用。
我们还可以将特定的状态变量传递给 useEffect
,让它在这些状态变量改变时触发。例如,如果我们只希望 useEffect
在 name
变量变化时运行,我们可以这样写:
useEffect(() => {
document.title = `Celebrate ${name}`;
}, [name]);
当 name
状态变化时,文档标题将更新为 "Celebrate {name}",其他状态变化不会触发此 useEffect
。
useEffect
现在我们添加一个新状态 admin
,并根据 admin
状态显示用户是否为管理员:
const [admin, setAdmin] = useState(false);
<p>{admin ? "Logged in" : "Not logged in"}</p>
<button onClick={() => setAdmin(true)}>Login</button>
我们可以使用另一个 useEffect
来监控 admin
状态的变化,并在状态变化时输出日志信息:
useEffect(() => {
console.log(`The user is ${admin ? "admin" : "not admin"}`);
}, [admin]);
当点击 "Login" 按钮时,admin
状态会改变,触发 useEffect
,输出 "The user is admin"。
useEffect
调用通过依赖项数组,我们可以确保每个 useEffect
只在对应的状态变量变化时触发。例如:
useEffect(() => {
console.log(`Celebrating ${name}`);
}, [name]);
useEffect(() => {
console.log(`The user is ${admin ? "admin" : "not admin"}`);
}, [admin]);
在此例中,name
变化时只触发第一个 useEffect
,而 admin
变化时只触发第二个 useEffect
。
使用依赖项数组可以帮助我们控制副作用的执行时机,避免不必要的代码运行。这使我们的应用更加高效和可控。
总结来说,通过使用依赖项数组,我们可以精确控制 useEffect
的执行时机,确保在状态变化时,只执行需要的副作用。
useEffect
进行数据获取useEffect
另一个常见的用例是用于数据获取。假设我们有一个 API,比如 api.github.com/users
,这是一个 REST API,返回一些 JSON 数据。我们可以使用 useEffect
结合 useState
来从 API 获取数据,并将其加载到页面中。
首先,我们从头开始构建组件,并导入 useState
和 useEffect
:
import { useState, useEffect } from "react";
接着,在 App
组件中创建一个 data
状态变量和更新它的 setData
函数,初始状态为一个空数组:
const [data, setData] = useState([]);
然后,我们可以在渲染时返回一段简单的文本,表示当前没有用户数据:
return <p>No users</p>;
useEffect
获取数据我们通过 useEffect
来在组件首次加载时从 API 获取数据:
useEffect(() => {
fetch("https://api.github.com/users")
.then((response) => response.json())
.then(setData);
}, []);
在这里,fetch
函数获取 API 数据,then
方法将返回的响应解析为 JSON,并将其传递给 setData
来更新组件的状态。
如果 data
中存在用户数据,我们希望将其显示为一个无序列表:
return (
<ul>
{data.map((user) => (
<li key={user.id}>{user.login}</li>
))}
</ul>
);
我们使用 map
方法遍历 data
中的每个用户,并根据 user.id
作为 key
,显示用户的登录名 user.login
。
为了防止 useEffect
在每次渲染时都发送请求,我们需要在 useEffect
中添加一个依赖项数组,确保该副作用只在组件首次加载时运行:
useEffect(() => {
fetch("https://api.github.com/users")
.then((response) => response.json())
.then(setData);
}, []); // 空数组确保仅在首次渲染时调用
接着,我们添加一个按钮,用来清空用户数据:
<button onClick={() => setData([])}>Remove Data</button>
点击按钮时,setData([])
将状态重置为空数组,清空页面上的用户数据。
如果我们移除依赖项数组,useEffect
会在每次状态更新时不断发送 API 请求,导致页面闪烁并不断重新加载数据。这就是为什么依赖项数组非常重要,它可以防止不必要的重复请求:
useEffect(() => {
fetch("https://api.github.com/users")
.then((response) => response.json())
.then(setData);
}, []); // 确保不会重复请求
在本例中,我们使用 useEffect
进行数据获取,并通过依赖项数组控制副作用的调用次数,避免重复的 API 请求。每次需要从外部获取数据时,记得使用依赖项数组,以确保不会发起多余的 HTTP 请求。
useReducer
管理状态除了 useState
,还有另一种管理状态变量的方法,那就是使用 React 中的另一个钩子 useReducer
。
useReducer
首先,我们导入 useReducer
并清理现有的代码,确保组件返回一个简单的 h1
元素:
import { useReducer } from 'react';
return <h1>{number}</h1>;
接下来,我们使用 useReducer
来管理数字状态。
useReducer
定义状态与 useState
不同,useReducer
需要两个参数:
0
。定义状态和更新状态的函数:
const [number, setNumber] = useReducer((number, newNumber) => number + newNumber, 0);
在这个例子中,第一个参数是一个接收当前状态 number
和更新值 newNumber
的函数。每次调用 setNumber
时,都会将当前数字和新数字相加,并返回新的状态。
为了让状态更新生效,我们可以在 h1
元素上添加一个点击事件处理函数。当用户点击 h1
时,调用 setNumber
函数,传递一个更新值:
<h1 onClick={() => setNumber(1)}>{number}</h1>
每次点击 h1
时,number
将增加 1。
useReducer
的主要结构包括:
每次调用 setNumber
,都会根据传入的新值更新状态。接下来的课程中,我们将深入理解 useReducer
,并通过将其他组件重构为使用 useReducer
来进一步探索它的工作原理。
useReducer
重构复选框状态管理在课程前面,我们创建了一个复选框,并通过 useState
来管理其选中状态。每次点击复选框时,我们调用 setChecked
函数来更新状态,并返回与当前状态相反的值。
现在,我们可以使用 useReducer
来重构这个功能,以减少潜在的 bug,特别是在与团队合作时。
useReducer
替换 useState
首先,我们从 useState
改为导入 useReducer
,并在代码中替换 useState
的使用:
import { useReducer } from 'react';
useReducer
函数我们将定义一个 toggle
函数来处理复选框状态切换。useReducer
需要两个参数:
false
,表示复选框初始为未选中状态。定义 reducer
函数和初始状态:
const [checked, toggle] = useReducer((checked) => !checked, false);
在这个例子中,reducer
函数接收当前状态 checked
,并返回其相反值,实现复选框的选中状态切换。
toggle
到复选框的 onChange
事件接着,我们将 onChange
事件的处理函数替换为 toggle
,使每次点击复选框时调用 toggle
函数更新状态:
<input type="checkbox" checked={checked} onChange={toggle} />
通过将状态管理逻辑抽象到 useReducer
中,我们不再需要在每个 onChange
事件中嵌入复杂逻辑。每次调用 toggle
都会执行状态切换,代码变得更加简洁和可维护。
随着状态变得更加复杂,使用 useReducer
可以帮助我们集中管理功能逻辑,使代码更易于理解和维护。
useReducer
和 dispatch
模式在使用 useReducer
时,我们可以定义一组操作(actions),并在组件中通过 dispatch
进行分发。这使得状态管理更加灵活和可维护。
首先,我们创建一个组件,显示消息的状态值。接下来,使用 useReducer
来管理这个消息状态。
import { useReducer } from "react";
const initialState = { message: "Hi" };
function reducer(state, action) {
switch (action.type) {
case "yell":
return { message: "HEY!" };
case "whisper":
return { message: "Excuse me..." };
default:
return state;
}
}
export default function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>{state.message}</p>
<button onClick={() => dispatch({ type: "yell" })}>Yell</button>
<button onClick={() => dispatch({ type: "whisper" })}>Whisper</button>
</div>
);
}
useReducer
函数useReducer
接收两个参数:
action
),根据操作的 type
返回新的状态。message: "Hi"
。const initialState = { message: "Hi" };
function reducer(state, action) {
switch (action.type) {
case "yell":
return { message: "HEY!" };
case "whisper":
return { message: "Excuse me..." };
default:
return state;
}
}
dispatch
触发操作我们通过按钮来触发不同的操作(yell
和 whisper
)。当用户点击按钮时,dispatch
会发送相应的操作类型:
<button onClick={() => dispatch({ type: "yell" })}>Yell</button>
<button onClick={() => dispatch({ type: "whisper" })}>Whisper</button>
有时候新状态依赖于之前的状态,我们可以在 reducer
函数中使用模板字符串,参考之前的 message
状态值:
case "yell":
return { message: `HEY! I just said ${state.message}` };
case "whisper":
return { message: `Excuse me... I just said ${state.message}` };
这样,每次更新状态时都会显示上一个状态的信息。
useReducer
函数适用于需要管理复杂状态的场景,它通过 dispatch
分发操作,并根据不同的操作类型更新状态。使用 reducer
函数可以让状态管理更加清晰,尤其是在处理依赖于之前状态的更新时。
useRef
处理表单数据React 提供的 useRef
钩子可以让我们访问 DOM 元素并读取其当前的值,这在处理表单时非常有用。在这个例子中,我们使用 useRef
来捕获用户输入的文本和颜色。
首先,我们导入 useRef
,并清理之前的代码。接着,我们创建一个包含文本输入框、颜色选择器和提交按钮的表单:
import { useRef } from 'react';
export default function App() {
const sound = useRef();
const color = useRef();
const submit = (e) => {
e.preventDefault(); // 防止表单默认的提交行为
const soundValue = sound.current.value;
const colorValue = color.current.value;
alert(`${soundValue} sounds like ${colorValue}`);
sound.current.value = ''; // 重置输入框
color.current.value = ''; // 重置颜色选择器
};
return (
<form onSubmit={submit}>
<input ref={sound} type="text" placeholder="Enter sound" />
<input ref={color} type="color" />
<button>Add</button>
</form>
);
}
通过 useRef
,我们可以为表单元素创建引用并访问它们的当前值。比如,我们创建了 sound
和 color
引用,分别用于文本输入框和颜色选择器:
const sound = useRef();
const color = useRef();
在表单的提交函数中,我们通过 sound.current.value
和 color.current.value
获取输入值,并将其显示为 alert
:
const soundValue = sound.current.value;
const colorValue = color.current.value;
alert(`${soundValue} sounds like ${colorValue}`);
提交表单后,我们可以通过将 current.value
设置为空字符串来重置输入框和颜色选择器:
sound.current.value = '';
color.current.value = '';
useRef
是处理表单和访问 DOM 元素的强大工具。在这个例子中,我们展示了如何使用 useRef
访问用户的输入数据,并通过 alert
显示这些数据,同时在提交后重置表单元素。这种模式可以帮助你捕获和处理用户输入,并将其传递给数据库或其他外部服务。
在 React 中,另一种处理表单输入的方式是使用受控组件。受控组件的表单输入通过状态变量进行管理,表单的值由状态决定。
useState
替代 useRef
首先,我们用 useState
替代之前的 useRef
来管理表单输入的状态:
import { useState } from "react";
export default function App() {
const [sound, setSound] = useState('');
const [color, setColor] = useState('#000000'); // 初始状态为黑色
const submit = (e) => {
e.preventDefault(); // 防止表单默认的提交行为
alert(`${sound} sounds like ${color}`);
setSound(''); // 重置输入框
setColor('#000000'); // 重置颜色选择器
};
return (
<form onSubmit={submit}>
<input
type="text"
placeholder="Enter sound"
value={sound}
onChange={(e) => setSound(e.target.value)}
/>
<input
type="color"
value={color}
onChange={(e) => setColor(e.target.value)}
/>
<button>Add</button>
</form>
);
}
在受控组件中,我们通过 value
属性来控制表单输入的值,同时使用 onChange
事件来更新状态。这里我们为每个输入框和颜色选择器分别绑定状态和事件处理:
value={sound}
绑定状态,并在 onChange
中调用 setSound
来更新状态。value={color}
绑定状态,并在 onChange
中调用 setColor
来更新状态。在表单提交时,我们通过 alert
显示当前的状态值,然后将输入框和颜色选择器的状态重置为初始值:
const submit = (e) => {
e.preventDefault(); // 防止页面刷新
alert(`${sound} sounds like ${color}`);
setSound(''); // 重置输入框
setColor('#000000'); // 重置颜色选择器
};
通过使用受控组件,表单的输入值完全由组件的状态控制。我们可以更灵活地管理表单数据,并确保输入的值在组件中始终是同步的。每次用户输入时,状态会更新,相应的值也会反映在输入框中。
useInput
除了使用 React 库提供的内置钩子外,你还可以根据需要创建自定义 Hook。当你发现多个组件中有重复逻辑时,可以将这些逻辑抽象到自定义 Hook 中。比如,我们在处理表单输入时,可以创建一个 useInput
Hook,来简化获取和重置输入值的逻辑。
useInput
首先,我们在项目的 src
文件夹下创建一个名为 useInput.js
的文件,并编写以下代码:
import { useState } from "react";
export default function useInput(initialValue) {
const [value, setValue] = useState(initialValue);
// 用于更新输入值的函数
const onChange = (e) => {
setValue(e.target.value);
};
// 用于重置输入值的函数
const reset = () => {
setValue(initialValue);
};
return [{ value, onChange }, reset];
}
useInput
的组件接下来,我们可以在表单组件中使用这个自定义 Hook 来替代之前的 useState
逻辑。我们会在输入字段中使用 useInput
,并将 value
和 onChange
处理逻辑抽象出来。
import useInput from './useInput';
export default function App() {
const [titleProps, resetTitle] = useInput(''); // 使用空字符串初始化
const [colorProps, resetColor] = useInput('#000000'); // 使用黑色初始化
const submit = (e) => {
e.preventDefault();
alert(`${titleProps.value} sounds like ${colorProps.value}`);
resetTitle(); // 重置输入框
resetColor(); // 重置颜色选择器
};
return (
<form onSubmit={submit}>
<input type="text" {...titleProps} placeholder="Enter sound" />
<input type="color" {...colorProps} />
<button>Add</button>
</form>
);
}
通过创建 useInput
Hook,我们将表单输入逻辑抽象为一个可复用的模块。每当我们需要处理表单输入时,只需调用 useInput
即可。这样可以减少代码重复,提高可维护性。具体好处包括:
useInput
可以在任何需要处理输入的地方使用,而不需要重复编写逻辑。自定义 Hook 允许你根据项目需求提取出常用的逻辑,使代码更易维护、更具复用性。在这个例子中,我们将表单输入逻辑封装到了 useInput
中,减少了重复代码的同时,也增强了组件的灵活性。
有时我们希望某些值可以在整个组件树中共享,而不必通过 props
一层一层传递数据。在这种情况下,我们可以使用 React 的 Context
,让所有子组件都可以访问这些数据。通过使用 React 的 createContext
和 Hooks,可以更加方便地处理这种场景。
在这个例子中,我们将创建一个 treesContext
,用于存储一些树的信息,并将其提供给整个应用中的组件:
import React, { createContext } from "react";
// 创建 treesContext
export const TreesContext = createContext();
// 数据,包含树的 ID 和类型
const trees = [
{ id: 1, type: "Maple" },
{ id: 2, type: "Oak" },
{ id: 3, type: "Family Tree" },
{ id: 4, type: "Component Tree" },
];
export default function App() {
return (
// 使用 TreesContext.Provider 来提供数据
<TreesContext.Provider value={trees}>
<div>
<h1>These are the trees I've heard of:</h1>
{/* 其他组件将通过 Context 来消费这些数据 */}
</div>
</TreesContext.Provider>
);
}
在上面的代码中,我们创建了 TreesContext
,并使用 TreesContext.Provider
包裹了整个应用的组件树。通过 Provider
的 value
属性,我们将 trees
数组传递给了子组件。这样,任何位于 Provider
之下的组件都可以访问这个上下文数据。
要在子组件中使用这些数据,我们可以使用 React 的 useContext
钩子来消费 treesContext
中的数据。让我们在下一步中实现这个功能。
在接下来的部分,我们将通过子组件展示这些树的信息。
useContext
消费上下文数据在上一节中,我们通过 Provider
将数据传递给子组件。这一次,我们将学习如何使用 useContext
钩子从上下文中读取数据。
首先,我们需要在组件中引入 useContext
钩子,并将 treesContext
中的数据传递给组件。我们将展示如何在 App
组件中使用这些数据。
import React, { useContext } from "react";
import { TreesContext } from "./index"; // 引入上下文
export default function App() {
// 使用 useContext 读取上下文数据
const trees = useContext(TreesContext);
return (
<div>
<h1>These are the trees I've heard of:</h1>
<ul>
{trees.map(tree => (
<li key={tree.id}>{tree.type}</li> // 显示每棵树的类型
))}
</ul>
</div>
);
}
useContext
:我们从 React 中引入 useContext
,并使用它来消费 treesContext
。useContext
读取数据:我们使用 useContext
来读取上下文提供的数据。在这个例子中,我们从 treesContext
中获取到树的数据,并将它存储在 trees
变量中。trees
数组,我们将每棵树的信息作为列表项渲染到页面上。通过这种方式,任何嵌套在 Provider
下的组件都可以使用 useContext
来读取共享的数据。无论组件在树中的位置如何,它们都能够访问这些数据,而无需通过 props
一层层传递。
这大大简化了在大型应用中管理数据的复杂性。
在 React 中,可以通过创建自定义 Hook 来简化使用上下文的流程。接下来,我们将介绍如何通过创建一个名为 useTrees
的自定义 Hook,使得获取上下文数据更加简洁和可复用。
useTrees
自定义 HookuseContext
:首先,在 index.js
文件中导入 useContext
。useTrees
的自定义 Hook,它会简化我们在组件中使用上下文的方式。// index.js
import React, { createContext, useContext } from 'react';
// 创建 TreesContext
const TreesContext = createContext();
// 定义自定义 Hook
export const useTrees = () => {
return useContext(TreesContext);
};
// 提供上下文数据
export default function TreesProvider({ children }) {
const trees = [
{ id: 1, type: 'Maple' },
{ id: 2, type: 'Oak' },
{ id: 3, type: 'Family Tree' },
{ id: 4, type: 'Component Tree' }
];
return (
<TreesContext.Provider value={trees}>
{children}
</TreesContext.Provider>
);
}
useTrees
自定义 Hook现在我们有了 useTrees
自定义 Hook,它简化了获取上下文数据的过程。我们可以在组件中直接调用 useTrees
而不需要每次手动使用 useContext
。
// App.js
import React from 'react';
import { useTrees } from './index'; // 使用自定义 Hook
export default function App() {
const trees = useTrees(); // 调用自定义 Hook 获取数据
return (
<div>
<h1>Trees I've heard of:</h1>
<ul>
{trees.map(tree => (
<li key={tree.id}>{tree.type}</li> // 显示树的类型
))}
</ul>
</div>
);
}
useTrees
Hook:自定义 Hook useTrees
内部使用 useContext
,将 TreesContext
中的数据直接提供给调用它的组件。App
组件中,我们不再需要显式调用 useContext
,而是通过 useTrees
直接访问上下文数据。这使得代码更加简洁和可复用。useContext
和上下文对象,只需要引入自定义 Hook 即可。通过创建自定义 Hook,我们使得从上下文中获取数据更加简便。这在需要在多个组件中使用相同上下文数据时尤其有用,减少了重复代码,提升了代码的可维护性和简洁性。
在这个视频中,我们将通过创建自定义 Hook 来实现数据的获取操作。这样,我们可以更加优雅地管理数据请求,并处理加载、成功和错误状态。
useFetch
自定义 HookuseFetch
是一个自定义 Hook,它将处理从 API 获取数据的所有逻辑,并返回加载状态、数据和错误信息。
// useFetch.js
import { useState, useEffect } from 'react';
// 定义 useFetch 自定义 Hook
export const useFetch = (uri) => {
// 定义状态变量
const [data, setData] = useState(null); // 数据
const [loading, setLoading] = useState(true); // 加载状态
const [error, setError] = useState(null); // 错误状态
// 使用 useEffect 进行数据获取
useEffect(() => {
if (!uri) return; // 如果没有 URI,直接返回
setLoading(true); // 设置加载状态为 true
// 执行数据请求
fetch(uri)
.then(response => response.json()) // 将响应解析为 JSON
.then(data => {
setData(data); // 设置获取到的数据
setLoading(false); // 设置加载状态为 false
})
.catch(error => {
setError(error); // 如果有错误,设置错误状态
setLoading(false); // 设置加载状态为 false
});
}, [uri]); // 当 URI 改变时,重新发起请求
// 返回加载状态、数据和错误状态
return { loading, data, error };
};
useFetch
自定义 Hook我们现在可以在任何组件中使用 useFetch
来获取数据。例如,下面的 App
组件会使用 useFetch
来从 GitHub API 获取用户数据,并根据不同的状态显示相应的内容。
// App.js
import React from 'react';
import { useFetch } from './useFetch';
export default function App() {
// 使用自定义 Hook 获取数据
const { loading, data, error } = useFetch('https://api.github.com/users');
// 渲染不同的状态
if (loading) return <h1>Loading...</h1>; // 加载中
if (error) return <p>Error: {error.message}</p>; // 错误
return (
<ul>
{data.map(user => (
<li key={user.id}>{user.login}</li> // 显示用户登录名
))}
</ul>
);
}
自定义 Hook useFetch
:
useFetch
接收一个 URI 作为参数,并根据这个 URI 发起 HTTP 请求。loading
(是否正在加载)、data
(请求返回的数据)和 error
(请求错误信息)。useEffect
会重新执行数据获取。在组件中使用 useFetch
:
App
组件调用 useFetch
并传入 GitHub API 的 URL 来获取用户数据。loading
、error
和 data
的不同状态,组件渲染不同的内容:加载时显示 "Loading...",如果发生错误则显示错误信息,成功则渲染用户列表。通过创建 useFetch
自定义 Hook,我们成功地将数据获取逻辑封装在一个通用的 Hook 中,使其可以在多个组件中重用。这不仅简化了代码,还提高了可维护性和可读性。
useFetch
Hook 来获取数据在这个视频中,我们已经创建了一个自定义的 useFetch
Hook,并在 App
组件中使用它来从 GitHub API 获取用户数据。以下是具体的步骤和代码展示:
App
组件在 index.js
文件中,我们将使用 useFetch
来获取用户数据并展示在页面上。以下是 App
组件的代码:
// App.js
import React from 'react';
import { useFetch } from './useFetch';
export default function App({ login }) {
// 使用自定义 Hook 获取数据
const { loading, data, error } = useFetch(`https://api.github.com/users/${login}`);
// 如果正在加载,显示加载状态
if (loading) return <h1>Loading...</h1>;
// 如果有错误,显示错误信息
if (error) return (
<pre>{JSON.stringify(error, null, 2)}</pre>
);
// 显示用户数据
return (
<div>
<img src={data.avatar_url} alt={data.login} />
<h1>{data.login}</h1>
{data.name && <p>Name: {data.name}</p>}
{data.location && <p>Location: {data.location}</p>}
</div>
);
}
useFetch
Hook我们使用 useFetch
来从 API 获取数据。它会返回三个状态变量:loading
(表示数据是否正在加载)、data
(获取到的数据)、error
(如果有错误,则显示错误信息)。
loading
为 true
时,显示 “Loading...”。index.js
中渲染 App
组件我们在 index.js
中将 App
组件渲染出来,并传入一个 login
属性作为 GitHub 用户名。
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<App login="your-github-username" />, // 传入 GitHub 用户名
document.getElementById('root')
);
loading
是否为 true
,决定是否显示加载状态。通过使用 useFetch
Hook,我们简化了组件内部的数据获取逻辑,并处理了数据加载的不同状态。这使得代码更加简洁、可复用,并且可以轻松地在整个应用程序中使用该自定义 Hook 进行数据获取。
感谢大家一路走来,一起学习 React Hooks 及其在各种场景中的应用。我们探讨了许多库,如 React Router、Relay、Apollo 等等,它们都将 Hooks 作为其 API 的一部分。本课程旨在为你打下基础,让你能够更好地探索 React 生态系统的其他领域。
如果你想了解更多关于开发自定义 Hooks 的内容,我强烈推荐一个很酷的网站——usehooks.com。在那里,你可以学习更多关于如何开发 Hooks 的信息,并获取一些实用技巧,将其应用到你自己的项目中。
非常感谢大家参与本课程,祝愿你在 React 学习之路上取得更大进展,继续前行!
React Hooks 深度探索 提升您的 React 技能 React Hooks Deep Dive Elevate Your React Skills
1 - Introduction 1 - Understanding modern React 2 - What You Should Know Before Watching This Course
2 - Intro to React Hooks 1 - React Hooks History 2 - Installing Create React App 3 - Reviewing the project
3 - The useState Hook 1 - Reviewing Array and Object destructuring 2 - Incorporating the useState Hook 3 - Building A Checkbox With Usestate 4 - Working with component trees 5 - Sending interactions up component trees
4 - The useEffect Hook 1 - Introducing useEffect 2 - Working with the dependency array 3 - Fetching Data With Useeffect
5 - Additional Hooks 1 - Handling Complex State With Usereducer 2 - Refactoring Usestate To Usereducer 3 - Dispatching Actions With Usereducer 4 - Managing Form Inputs With Useref 5 - Creating controlled components with useState
6 - Creating Custom Hooks 1 - Reusing form logic with custom Hooks 2 - Placing Data In Context 3 - Retrieving Data With Usecontext 4 - Creating A Custom Hook With Context 5 - Data fetching with a Fetch Hook 6 - Building a fetch component
7 - Conclusion 1 - Next Steps
1 - 介绍 1 - 理解现代 React 2 - 观看本课程前你需要了解的内容
2 - React Hooks 介绍 1 - React Hooks 的历史 2 - 安装 Create React App 3 - 项目回顾
3 - useState Hook 1 - 回顾数组和对象解构 2 - 引入 useState Hook 3 - 用 useState 构建复选框 4 - 处理组件树 5 - 将交互向上传递到组件树
4 - useEffect Hook 1 - 介绍 useEffect 2 - 使用依赖数组 3 - 使用 useEffect 获取数据
5 - 其他 Hooks 1 - 用 useReducer 处理复杂状态 2 - 将 useState 重构为 useReducer 3 - 使用 useReducer 派发操作 4 - 使用 useRef 管理表单输入 5 - 用 useState 创建受控组件
6 - 创建自定义 Hooks 1 - 用自定义 Hooks 复用表单逻辑 2 - 将数据放入 Context 3 - 使用 useContext 获取数据 4 - 使用 Context 创建自定义 Hook 5 - 用 Fetch Hook 获取数据 6 - 构建一个获取数据的组件
7 - 总结 1 - 下一步