React 和 Next.js 简介

原文地址: Just Enough React (and Next.js)

翻译: JulySong

在新手篇教程中,我们构建了一个非常简单的 dApp,它使用了 HTML、CSS 和一些 Javascript。然而,在现实世界中,这些类型的“普通”网站实现已成为过去。

今天,我们使用 Web 框架来简化 Web 开发过程。它更简单吗?取决于你从谁的角度来看。如果您刚刚开始并且以前从未这样做过,那么您可能需要一些时间才能理解所有必要的概念。但是,从长远来看,您会感谢自己并为您花时间学习它而感到高兴。

当今使用的最大和最常见的 Web 框架是:

尽管它们各有优缺点,但 React 迄今为止已经席卷了 Web 开发世界。在 Web3 领域也是如此,其中 React 是构建 dApp 最常用的 Web 框架。在整个入门篇教程以及所有后续教程中,我们将使用大量 React,因此将此级别视为 React 速成课程,它将教你足够的入门知识。这并不是要替代在专注于 Web2 教学的平台上学习 React,而是用作了解入门需要多少知识的指南,这样您就不会陷入教程地狱。😅

实际上,我们将使用 Next.js——它是 React 本身的扩展——但稍后会详细介绍。让我们先学习一些 React。

WTF 是 React?

React 是一个 Web 框架,可以轻松构建和推理 Web 应用程序的“视图”。“视图”是屏幕上显示的内容,它如何变化、如何更新等。React 本质上只是为您提供了一种模板语言,您可以创建返回 HTML 的 Javascript 函数。

普通的 Javascript 函数返回 Javascript 风格的东西——字符串、数字、布尔值、对象等。React 基本上结合了 Javascript 和 HTML 来产生一种他们称为 JSX 的语言。在 JSX 中,类似 Javascript 的函数返回 HTML 而不是常规的 Javascript 内容。基本上就是这样。

返回 HTML 的 Javascript 函数的组合称为组件。组件是用 JSX 编写的。虽然一开始看起来很尴尬,但一旦你习惯了,它们实际上很容易使用。

这是一个简单组件的示例:

1
2
3
4
5
6
7
8
9
10
11
12
function ShoppingList() {
return (
<div className="shopping-list">
<h1>Shopping List</h1>
<ul>
<li>Apples</li>
<li>Bananas</li>
<li>Grapes</li>
</ul>
</div>
);
}

很好,但这与 HTML 没有太大区别。但是,如果您想基于数组呈现项目列表怎么办?

1
2
3
4
5
6
7
8
9
10
11
12
function ShoppingList() {
const items = ["Apples", "Bananas", "Grapes"]

return (
<div className="shopping-list">
<h1>Shopping List</h1>
<ul>
{items.map((item, index) => <li key={index}>{item}</li>)}
</ul>
</div>
);
}

哇,看那个!我们只是在 HTML 中使用了 Javascript。在 JSX 中,您可以通过将 JS 代码包装在花括号{} 中来在 HTML 中编写 Javascript。但实际上,如果您再考虑一下,您就会意识到正在发生的事情。.map() 是一个 Javascript 函数,它遍历一个数组并为每个项目返回一些内容。在这种情况下,它循环遍历 items 数组并返回一个 li 元素,该元素具有其中的 Javascript 变量 item 的值,即 HTML。得到它?🤔

在我们的组件内部,我们基本上嵌入了另一个组件。map 函数是一个返回 HTML 的 JS 函数。它是一个组件。即使它没有明确定义为顶级函数,它仍然是一个组件。

将组件嵌入到其他组件中是 React 的强大功能。这称为组合(Composition)。我们将多个返回 HTML 的 Javascript 函数组合在一起,并从中构建一个组合的 HTML 文档,该文档将显示在 Web 应用程序上。

组件之间的数据传递

如果组件只是静态的,它们就不是很有用。当然,遍历数组和其他东西都很好,但当今大多数 Web 应用程序都不是静态文档。今天的大多数 Web 应用程序都会从某种服务器、数据库或区块链中动态获取数据。这意味着经常需要相同的组件来显示不同的数据。

拥有组件的主要用例是能够编写可重用的代码,并且可以在其中包含不同的信息,而无需再次重写整个代码。

让我们看一个例子。这两个代码哪个更易读?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<div class="cards">
<div class="card">
<img src="img_avatar.png" alt="Avatar">
<div class="container">
<h4><b>Alice</b></h4>
<p>Frontend Developer</p>
</div>
</div>

<div class="card">
<img src="img_avatar.png" alt="Avatar">
<div class="container">
<h4><b>Bob</b></h4>
<p>Backend Developer</p>
</div>
</div>

<div class="card">
<img src="img_avatar.png" alt="Avatar">
<div class="container">
<h4><b>Charlie</b></h4>
<p>Full Stack Developer</p>
</div>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function Cards() {
return (
<div className="cards">
<!--Data is passed to children through HTML attributes-->
<Card name="Alice" job="Frontend Developer" />
<Card name="Bob" job="Backend Developer" />
<Card name="Charlie" job="Full Stack Developer" />
</div>
)
}

// Card receives an object as an argument
// We can destructure the object to get specific variables
// from inside the object - name and job
function Card({name, job}) {
return (
<div className="card">
<img src="img_avatar.png" />
<div className="container">
<h4><b>{name}</b></h4>
<p>{job}</p>
</div>
</div>
)
}

纯 HTML 示例重复使用相同的代码 3 次,尽管真正改变的只是人名和他们的职位。

在 JSX 中,我们可以将每个 Card 抽象为一个组件,该组件从其父组件(在本例中为 Cards)获取某些数据。父组件通过类似 HTML 的属性(name="Alice")将数据传递给子组件,子组件访问它们的值就像 JS 函数接收 JS 对象作为参数一样。然后 Card 组件可以根据从父级接收到的内容返回带有可变数据的 HTML。

这段代码更容易重用和扩展。想要稍微改变所有卡片的外观吗?只需修改一个组件!并非所有复制粘贴的 HTML。

交互式组件

好的,所以我们现在可以在组件之间传递数据。这一切都很好,但我们还没有添加交互性。诸如在单击按钮或在输入框中键入文本时能够运行一些代码等。

值得庆幸的是,在 Javascript 中,函数可以在其中包含函数。例如,

1
2
3
4
5
6
7
8
9
10
11
12
function someFunc() {

function otherFunc() {
console.log("Hello!")
}

otherFunc();
}

someFunc(); // will print "Hello!"

otherFunc(); // will throw an error! undefined here

otherFunc 只能在 someFunc 自身内部使用。这是常规 Javascript 中很少使用的功能,但在使用 React 时使用非常频繁。让我们通过一个例子来看看为什么。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Button() {

function handleClick() {
console.log("Hello")
}

return (
<button
className="button"
onClick={handleClick}>
Click Me!
</button>
)
}

我们有一个名为 Button 的 JSX 函数。在这个函数中,我们有另一个函数叫做 handleClick。在 HTML <button> 标记中,我们指定 onClick={handleClick} 以便在单击按钮时调用 handleClick 函数。此功能仅在 Button 组件内部可用。单击 Web 应用程序上的按钮将在浏览器控制台中打印 Hello。这就是我们使用 React 构建交互式网站的方式!

这个例子仍然相当简单,因为 handleClick 没有参数。如果我们想在用户输入输入框时在控制台中打印文本怎么办?我们如何将文本传递给函数?

就是这样。

1
2
3
4
5
6
7
8
9
10
11
12
13
function PrintTypedText() {

function handleOnChange(text) {
console.log(text);
}

return (
<input
type="text"
onChange={(e) => handleOnChange(e.target.value)}
/>
)
}

HTML input元素提供了一个方便的事件侦听器 - onChange - 每次该输入框中的文本发生更改(键入新字符、删除字符等)时都会触发该事件侦听器。

但是,除了触发一个函数之外,它还传递了更改的 HTML 元素(此处称为 e)。然后,我们可以获取 HTML 元素 e 并使用 e.target.value 提取文本,并将其作为参数传递给 handleOnChange,它将文本记录到浏览器控制台。

不同的 HTML 元素有不同的事件处理程序 - onChangeonClick 是两个例子,但还有更多!您可以在此处找到所有 HTML 事件的列表。

通过将 HTML 事件与函数处理程序相结合,我们可以做各种很酷的事情!从服务器加载数据、向服务器发送数据、更新我们的视图等。

React Hooks - useState 和 useEffect

好的,我们已经讨论了组合、数据传递和交互性。但是,我们的应用程序仍然很愚蠢。交互性将允许您在单击按钮等时运行一些代码,但是如果您想更新一些变量怎么办?

不幸的是,以下不起作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function DoesNotWork() {
let myNumber = 0;

function increment() {
myNumber++;
}

return (
<div>
<p>{myNumber}</p>
<button onClick={increment}>Increment!</button>
</div>
)
}

无论你点击多少次 Increment 按钮,屏幕上显示的数字都会卡在 0。这是因为当你从 React 组件中更新 myNumber 等常规变量时,即使值更新了,React 实际上并没有重新渲染 Web 应用程序的视图。它不会自动更新页面的 HTML 视图。

React Hooks 是“挂钩”到 React 组件的不同部分的函数,允许您执行诸如在变量的值更改时更新视图,或者在每次加载页面或更改变量时自动运行一些 JS 代码等操作,以及许多更酷的东西!我们将主要关注 95% 的时间使用的三个 React 钩子 - useStateuseEffectuseRef

useState

很多时候,您希望 HTML 视图根据某些变量的值变化进行更新。我们可以使用 useState 钩子来维护一个变量,该变量会在每次更改其值时自动重新渲染屏幕上显示的 HTML。这是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function ThisWorks() {
// myNumber is the variable itself
// setMyNumber is a function that lets us update the value
// useState(0) initializes the React Hook
// with the starting value of 0
const [myNumber, setMyNumber] = useState(0);

function increment() {
// Sets the new value to the old value + 1
setMyNumber(myNumber + 1);
}

return (
<div>
<p>{myNumber}</p>
<button onClick={increment}>Increment!</button>
</div>
)
}

如果您尝试运行上述代码,您将看到 Web 应用程序的视图自动更新以反映变量的新值。

useState 在 React 中使用创建的变量称为状态变量。状态变量可以更新并自动更新应用程序的视图。这是另一个在输入框中使用状态变量的示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function StateWithInput() {
// myName is the variable
// setMyName is the updater function
// Create a state variable with initial value
// being an empty string ""
const [myName, setMyName] = useState("");

function handleOnChange(text) {
setMyName(text);
}

return (
<div>
<input type="text" onChange={(e) => handleOnChange(e.target.value)} />
<p>Hello, {myName}!</p>
</div>
)
}

关于 useState,我想说的最后一件事是,您还可以将它用于字符串和数字等基本类型。您还可以使用它们来存储数组和对象。但是,这里有一个警告。让我们看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function StateArrayDoesNotWork() {
const [fruits, setFruits] = useState([]);
const [currentFruit, setCurrentFruit] = useState("");

function updateCurrentFruit(text) {
setCurrentFruit(text);
}

function addFruitToArray() {
fruits.push(currentFruit);
}

return (
<div>
<input type="text" onChange={(e) => updateCurrentFruit(e.target.value)} />
<button onClick={addFruitToArray}>Add Fruit</button>

<ul>
{fruits.map((fruit, index) => (
<li key={index}>{fruit}</li>
))}
</ul>
</div>
);
}

如果您尝试运行它,您将看到屏幕上没有显示任何水果。另请注意,我们没有在 setFruits 任何地方使用该函数,而只是尝试.push 使用 fruits 数组。

当我们尝试直接更新数组时,React 不会注册状态更改,这也被认为是无效的状态更新,可能导致应用程序出现意外行为。我们知道我们需要以某种方式使用 setFruits,但是如何使用呢?答案是我们实际上需要创建水果数组的副本,将水果添加到其中,并将状态变量完全设置为新数组。下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function StateArray() {
const [fruits, setFruits] = useState([]);
const [currentFruit, setCurrentFruit] = useState("");

function updateCurrentFruit(text) {
setCurrentFruit(text);
}

function addFruitToArray() {
// The spread operator `...fruits` adds all elements
// from the `fruits` array to the `newFruits` array
// and then we add the `currentFruit` to the array as well
const newFruits = [...fruits, currentFruit]
setFruits(newFruits);
}

return (
<div>
<input type="text" onChange={(e) => updateCurrentFruit(e.target.value)} />
<button onClick={addFruitToArray}>Add Fruit</button>

<ul>
{fruits.map((fruit, index) => (
<li key={index}>{fruit}</li>
))}
</ul>
</div>
);
}

如果您尝试运行上述代码,您将看到它按预期工作。每次按下按钮时,输入框中的当前文本都会添加到数组中,这会导致 HTML 上显示的水果列表更新。您可以继续添加任意数量的水果!

对象也是如此。如果您的状态变量包含一个对象,您需要先创建该对象的副本,更新一个值,然后将状态变量完全设置为新对象。

useEffect

所以我们现在可以管理状态,太好了!状态变化也会影响我们渲染的 HTML,也很棒!

但是,通常需要在第一次加载页面时自动运行一些代码——也许是为了从服务器或区块链获取数据——并且还需要在某个状态变量发生变化时自动运行一些代码。

这些类型的功能称为副作用。React 为我们提供了 useEffect 钩子,它允许我们编写这些效果。useEffect 有两个参数——一个函数和一个依赖数组。函数是运行效果时运行的代码,依赖数组指定何时触发副作用。

考虑一个示例,当网站首次加载时,它想从服务器加载一些数据。这样做时,它希望向用户显示一个加载屏幕,然后在加载数据后,移除加载屏幕并显示实际内容。我们如何做到这一点?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function LoadDataFromServer() {
// 创建一个状态变量来保存从服务器返回的数据
const [data, setData] = useState("");
// 创建一个状态变量来保持加载状态
const [loading, setLoading] = useState(false);

async function loadData() {
// 将 `loading` 设置为 `true` 直到 API 调用返回响应
setLoading(true);

// 执行 API 调用以从服务器加载数据的虚构函数
const data = await apiCall();
setData(data);

// 我们有了数据,将 `loading` 设置为 `false`
setLoading(false);
}

// loadData 是运行的函数
// 一个空的依赖数组意味着这段代码在页面加载时运行一次
useEffect(() => {
loadData();
}, []);

// 当 `loading` 为 `true` 时显示 `"Loading..."`,
// 否则显示 `data`
return <div>{loading ? "Loading..." : data}</div>;
}

如果您从链接运行上述代码,您将看到它在屏幕上显示 Loading... 5 秒钟,然后显示 ABCDEF。这是因为 apiCall 是一个只等待 5 秒然后返回字符串 ABCDEF 的函数。

首次加载页面时 useEffect 调用 loadData - 由于依赖项数组为空 - 并且状态变量使 HTML 呈现适当的内容。

这对于在页面第一次加载时运行代码很有用,但是每次状态变量的值发生变化时运行一些代码呢?例如,当您在 Facebook 上搜索某人的姓名时,Facebook 如何在您每次添加/删除角色时获取并显示推荐?

您也可以使用 useEffect 来做到这一点,方法是在依赖数组中提供状态变量。每次该变量的值发生变化时,都会运行效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function DependentEffect() {
const names = ["Alice", "Bob", "Charlie", "David", "Emily"];

const [recommendations, setRecommendations] = useState([]);
const [searchText, setSearchText] = useState("");

useEffect(() => {
// 如果用户没有搜索任何内容,则不显示任何推荐
if (searchText.length === 0) {
setRecommendations([]);
}
// 否则,查找推荐
else if (searchText.length > 0) {
const newRecs = names.filter((name) =>
name.toLowerCase().includes(searchText.toLowerCase())
);
setRecommendations(newRecs);
}
}, [searchText]);

return (
<div>
<input type="text" onChange={(e) => setSearchText(e.target.value)} />
<h2>Recommendations:</h2>
<ul>
{recommendations.map((rec, index) => (
<li key={index}>{rec}</li>
))}
</ul>
</div>
);
}

如果您运行上述代码并尝试输入一些字母,您将看到推荐列表会随着您在搜索框中添加/删除新字符而自动更新。这是因为当您更新输入框时,searchText 的值会通过 onChange 处理程序进行更新,该处理程序会触发 useEffect,它会更新 recommendations 列表,从而更新 HTML 视图。

您也可以类似地创建依赖于多个状态变量的副作用,而不仅仅是一个。如果任何因变量发生变化,就会产生副作用。您只需将更多状态变量添加到依赖项数组即可。

useRef

useRef 是另一个比较常用的 React 钩子。它表面上与 useState 非常相似,但有一些实际上非常重要的细微差别使得这个 React Hook 学习起来很重要。

useRef 变量的创建方式如下:

1
2
3
4
5
6
7
8
9
10
11
function Component() {
const myValue = useRef();

function updateMyValue(newValue) {
myValue.current = newValue;
}

function printMyValue() {
console.log(myValue.current);
}
}
1. 没有重新渲染

useState 类似,useRef 挂钩也允许我们将变量存储在组件中,该变量可以随时间更新。但是,与状态变量不同,更新 ref 变量的值不会导致 HTML 视图重新呈现。

因此,如果您有一个 useRef 变量并且您在 HTML 视图中显示它的值,则更新该变量不会更新 HTML 视图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function CounterWithRef() {
const myNumber = useRef();

function increment() {
if (myNumber.current !== undefined) {
myNumber.current += 1;
} else {
myNumber.current = 1;
}
console.log(myNumber.current);
}

return (
<div>
<p>{myNumber}</p>
<button onClick={increment}>Increment!</button>
</div>
)
}

如果您运行上面的代码,您会注意到每次单击按钮时,值都会递增并打印在浏览器控制台中,但 HTML 视图实际上并没有更新。事实上,HTML 视图不显示任何内容,因为 myNumber.current 的初始值是undefined的,并且由于 HTML 没有更新,因此就 HTML 而言,即使值实际上正在更新,它仍保持undefined

2. 同步更新


关于 useState,我们之前没有提到的是,当我们使用 setXYZ 函数更新状态变量时,它实际上不会立即更新。

在 React 中为状态变量设置新值是异步发生的,这意味着如果您在将状态变量设置为新值后立即尝试使用它的值,您可能实际上看不到新值被反映,因为它是异步发生的。

我们再来看看使用 useState 时的 Counter 例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function AsyncStateVariables() {
const [number, setNumber] = useState(0);

function increment() {
setNumber(number + 1);
console.log(number);
}

return (
<div>
<p>{number}</p>
<button onClick={increment}>Increment</button>
</div>
);
}

当您运行它时,请注意视图上发生了什么以及控制台中发生了什么。当您第一次单击按钮时,状态变量应该更新为 1 - 这就是视图上发生的情况,网页显示 1。但是如果您查看浏览器控制台,则会打印值 0 而不是 1。这种模式继续单击按钮。

这是因为 setNumber 调用是异步运行的,当我们到达 console.log(number) 行时,该值还没有更新,所以它打印了 number 的旧值。当它确实被更新时,HTML 被重新渲染以显示新值。


另一方面,useRef 允许同步更新。当您使用 myVar.current = newValue 更新参考变量的值时,它会立即更新,并且没有延迟。这有时会派上用场。

3. 引用 DOM 元素

useRef 让我们做的另一件很酷的事情是它允许我们直接引用 DOM 元素。这是 useState 无法实现的。

例如,您可以使用 useRef 直接引用input元素

1
2
3
4
5
6
7
8
9
function InputFocus() {
const inputRef = useRef();

useEffect(() => {
inputRef.current.focus();
}, []);

return <input ref={inputRef} type="text" />;
}

当您运行上面的示例时,您会注意到页面加载后,该 input 元素已经在焦点上,即您可以开始输入而无需先单击它。这是因为我们持有对 input 元素的引用,并且由于有一个空的依赖数组,因此在页面加载时运行的 useEffect 专注于 input 元素。

React 文件结构

太好了,如果你刚刚开始使用 React,我们已经介绍了你应该知道的主要概念。但到目前为止,我们只处理了孤立的组件示例。一个实际的 React 项目是什么样子的?

React 应用程序通常使用 create-react-app(CRA)之类的工具创建。CRA 是一个命令行工具,可帮助您设置新的 React 项目并安装所有必需的依赖项,而无需手动创建所有样板。

当您使用 CRA 时,您最终会得到如下所示的文件结构。

package.json 文件应该是可识别的。CRA 通过 Node.js 环境工作,而 package.json 是创建所有依赖项和项目元数据的地方 - 与任何其他 Node.js 项目一样。

src/ 文件夹包含组件和 CSS 样式,基本上是任何特定于 React 的代码。这里的主要组件是App.js,它是您第一次设置 React 应用程序时创建的自动生成的组件。index.js 是 React 应用程序的主要入口点,但通常您不需要太多(或根本不需要)修改它。它只包含一些样板 React 代码,这些代码获取您的组件并将其转换为可以在浏览器中运行的实际 HTML 和 JS。

然后,public/文件夹默认只包含一个文件 - index.html。你通常不会自己碰这个。这是一个超级简单的准系统 HTML 文件。当一个 React 应用程序运行时,React 会在后台执行一些魔法,它会获取所有组件和 Javascript 代码,将其转换为可以在浏览器中运行的实际 HTML 和 JS,并用所有这些替换 index.html 的内容。然后,更新后的 index.html 就是向用户显示的内容。

如果您想将图像、字体、音乐等添加到您的网站,它们也会进入 public/ 文件夹。public/ 文件夹基本上包含您希望在您的网站上直接访问的所有内容。

例如,如果您在 public/ 文件夹中添加了一个名为 avatar.png 的图像,那么您可以在 React 组件中显示该图像,如下所示:

1
<img src="/avatar.png" />

虽然这可能看起来很奇怪,因为您的组件位于 src/ 文件夹中而不是 public/ 文件夹中——它起作用的原因是因为图像与 index.html 位于同一文件夹中——而 index.html 是你的 React 代码实际结束的地方向上。因此,当使用 /avatar.png 的相对路径引用图像时,它知道 avatar.png 必须在public文件夹内。

后端时间

到目前为止,我们一直在讨论 React,以及它的所有前端功能。但是后端呢?

React 不是后端框架,因此如果您想创建自己的 API 后端,则必须使用 Node.js 和 Express 之类的东西建立一个单独的项目。然而这很麻烦,就好像后端和前端是同一个项目一样,你可能有很多代码可以在两者之间重用和共享。此外,维护两个项目总是比只维护一个项目更难。

输入,Next.js

接下来是 React 的元框架。这是什么意思?嗯,React 本身就是一个构建 Web 应用程序的框架。接下来是一个 React 框架,它还引入了一些 React 没有的附加功能。

如果你了解 React,那么 Next 90% 是完全一样的东西,你可以很快开始使用它,但是我想谈谈 Next 带来的这些额外功能。

首先,正如标题和介绍所暗示的那样,Next 允许您在单个项目中编写前端和后端代码。您使用 React 构建前端,并使用与使用 Express 类似的语法编写后端 API 端点 - 但都在同一个项目中。

其次,Next 使创建多页 Web 应用程序变得非常容易。React 最初旨在帮助创建单页应用程序(SPA),组件非常适合!但是,如果您的网站有多个页面怎么办?例如https://learnweb3.io/https://learnweb3.io/abouthttps://learnweb3.io/tracks等等。

为此,React Router 引入了诸如此类的库,这使之成为可能,但也有点麻烦。“下一步”通过允许基于文件名的自动页面路由大大简化了这一点。

最后,Next 还具有服务器端渲染 (SSR) 和静态站点生成 (SSG)。这些不是我们将在我们的曲目中使用的功能,所以我不会在这里花太多时间,但如果您想了解更多关于它们的信息,请随时阅读推荐阅读。

Next 路由

在讨论创建后端服务器之前,我们将讨论路由,因为这将帮助您了解它是如何工作的。

create-react-app 类似,Next 有一个名为 create-next-app 的工具,可以自动帮助您轻松设置新的 Next.js 项目。

当您创建一个新的 Next.js 项目时,您最终会得到一个如下所示的文件结构:

这是很多文件!但别担心,其中很多与我们已经讨论过的 React 类似。

public/文件夹的工作方式完全相同,但不包含 index.html 文件。但是,如果您想添加图像、图标、字体、音乐等,您可以将它们全部放在 public/文件夹中。

styles/ 文件夹是一个很好的补充,为您的所有 CSS 文件提供了一个专用位置。

pages/ 很棒。_app.js 是一个自动生成的文件,通常你不会自己去碰它,它设置了一些样板代码,允许 Next 渲染正确的组件。

pages/index.js 是您网站的主页。基本上,文件 pages 夹中的每个文件都是您网站的路线。遵循 Javascript/HTML 样式的命名约定,这意味着 index 文件是“主”文件。因此,pages/index.js 是您首次打开网站时将加载的视图。

如果您在 pages 文件夹下添加更多文件,例如名为 about.js 的文件 - 它将在 YOUR_DOMAIN/about 中可用(有趣的事实:LearnWeb3 的网站是使用 Next 创建的,这正是我们的https://learnweb3.io/about 页面工作)。

这很棒,因为您不必处理诸如 React Router 之类的事情,并且构建多页面网站就像在 pages/文件夹下创建一个新文件一样简单,Next 会自动为您生成基于文件名的路由。

您还可以通过在文件夹下创建子文件夹来进行多级路由 pages/。例如,类似 pages/tracks/freshman.js 的东西会有 route YOUR_DOMAIN/tracks/freshman

在 Next 中编写 API

pages/ 下有一个特殊文件夹,但是它也是自动生成的。pages/api 文件夹。与呈现 HTML 视图的常规页面不同,pages/api 文件夹下的任何内容都充当 API 端点。

让我们看一下自动生成的 pages/api/hello.js 文件:

1
2
3
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' })
}

这是一个非常类似于 Express 的功能。如果您要转到 YOUR_DOMAIN/api/hello - 而不是呈现 HTML 视图,这将返回一个 JSON 对象 {name: 'John Doe'} - 这是一个超级简单的 API 端点。

与常规 HTML 视图类似,您可以通过创建新文件在 pages/api 下创建 API 端点,并且端点路由基于文件名。

结论

我希望这篇文章对您有所帮助,并且可以作为速成课程。我故意在这里更多地关注 React 而不是 Next,因为习惯前端部分对我们来说将比后端部分更相关。此外,后端代码基本上是常规的 Javascript,而前端是 JSX,我想让你更熟悉它。

我列出了一些额外的阅读材料和视频,我建议这些阅读材料和视频可以更好地理解这些概念。与往常一样,如果您有任何问题,请在#web2-support 的频道上留言,我们很乐意为您提供帮助。

读物/视频


练习题

  1. 🤔 JSX 的强大之处是什么?

    A: 它是 Javascript 的扩展,增加了静态类型

    B: 它是 Javascript 的扩展,允许您从 JS 函数返回 HTML 元素

    C: 它是一个 Web 框架,可以帮助您推断应用程序的视图

  2. 🤔 什么是组合(Composition)?

    A: 它是多个组件的组合,以创建单个分层 HTML 文档

    B: 它是 React 库的依赖构成

    C: 这是 React 官方歌曲

  3. 🤔 你可以通过 props 向组件传递任意数据吗?

    A: 是

    B: 否

  4. 🤔 React 组件如何让你的代码更容易理解?

    A: 通过使用 Github Copilot 自动为您生成代码

    B: 通过数据传递实现组件的可重用性

    C: 使用更简单的编程语言

  5. 🤔 以下代码在运行时会做什么?

    A: 返回数字 3

    B: 因错误而崩溃

  6. 🤔 点击增量按钮会发生什么?

    A: 应用程序的视图将更新以显示新的递增值 num

    B: 什么都不会发生

    C: 应用程序会崩溃

  7. 🤔 为什么在 React 中使用状态变量(useState)?

    A: 它们用于维护在值更改时重新呈现 HTML 的值

    B: 它们只是语法糖,没有真正的用途

    C: 它们是在 React 组件中定义变量的唯一方法

  8. 🤔 useEffect hook 只能在页面首次加载时运行?

    A: 是

    B: 否

  9. 🤔 点击开始按钮会发生什么?

    A: num的值加一并且 HTML 视图更新以显示新值

    B: 什么都没发生

    C: React 应用程序陷入无限循环

  10. 🤔 useEffect 只能依赖一个值?

    A: 是

    B: 否

  11. 🤔 useRef 和 useState 有什么区别?

    A: 对引用变量的更新是同步完成的,而对状态变量的更新是异步完成的

    B: 没有区别

    C: useRef 仅用于引用 DOM 元素,而 useState 用于维护变量值

  12. 🤔 在网页间路由方面,React 和 Next.js 的主要区别是什么?

    A: React 自动进行基于文件的路由,而 Next 需要一个路由库

    B: 它们完全一样

    C: React 需要一个路由库,而 Next 自动执行基于文件的路由

  13. 🤔 如何在 Next.js 应用程序中编写 REST API 端点?

    A: 通过在 pages/ 子目录中创建文件

    B: 通过在 pages/api/ 子目录中创建文件

    C: 通过在 api/ 子目录中创建文件

  14. 🤔 React 组件在 JSX 中使用时必须以大写字母开头?

    A: 是

    B: 否

参考答案:

  1. B
  2. A
  3. A
  4. B
  5. B
  6. B
  7. A
  8. B
  9. C
  10. B
  11. A
  12. C
  13. B
  14. A