作者选择Creative Commons接受捐赠,作为Write for DOnations计划的一部分。
介绍
在本教程中,您将学习在React 中创建自定义组件。组件是您可以在应用程序中重用的独立功能,并且是所有 React 应用程序的构建块。通常,它们可以是简单的JavaScript 函数和类,但您可以像使用自定义的 HTML 元素一样使用它们。按钮、菜单和任何其他前端页面内容都可以创建为组件。组件还可以包含状态信息和显示降价。
在学习了如何在 React 中创建组件后,您将能够将复杂的应用程序拆分为更易于构建和维护的小块。
在本教程中,您将创建一个表情符号列表,单击时会显示其名称。表情符号将使用自定义组件构建,并将从另一个自定义组件内部调用。在本教程结束时,您将使用 JavaScript 类和 JavaScript 函数制作自定义组件,并且您将了解如何将现有代码分成可重用的部分以及如何将组件存储在可读的文件结构中。
先决条件
-
你需要一个运行Node.js的开发环境;本教程在 Node.js 版本 10.20.1 和 npm 版本 6.14.4 上进行了测试。要在 macOS 或 Ubuntu 18.04 上安装它,请按照如何在 macOS 上安装 Node.js 和创建本地开发环境或如何在 Ubuntu 18.04 上安装 Node.js 的使用 PPA 安装部分中的步骤进行操作。
-
您将需要能够使用Create React App创建应用程序。您可以在如何使用 Create React App设置 React 项目中找到使用 Create React App 安装应用程序的说明。
-
您还需要具备 JavaScript 的基本知识(可以在How To Code in JavaScript 中找到),以及 HTML 和 CSS 的基本知识。关于 HTML 和 CSS 的一个很好的资源是Mozilla Developer Network。
步骤 1 — 设置 React 项目
在此步骤中,您将使用 Create React App 为您的项目创建基础。您还将修改默认项目以通过映射表情符号列表并添加少量样式来创建基础项目。
首先,创建一个新项目。打开终端,然后运行以下命令:
- npx create-react-app tutorial-03-component
完成后,切换到项目目录:
- cd tutorial-03-component
App.js
在文本编辑器中打开代码:
- nano src/App.js
接下来,取出 Create React App 创建的模板代码,然后将内容替换为显示表情符号列表的新 React 代码:
import React from 'react';
import './App.css';
const displayEmojiName = event => alert(event.target.id);
const emojis = [
{
emoji: '😀',
name: "test grinning face"
},
{
emoji: '🎉',
name: "party popper"
},
{
emoji: '💃',
name: "woman dancing"
}
];
function App() {
const greeting = "greeting";
const displayAction = false;
return(
<div className="container">
<h1 id={greeting}>Hello, World</h1>
{displayAction && <p>I am writing JSX</p>}
<ul>
{
emojis.map(emoji => (
<li key={emoji.name}>
<button
onClick={displayEmojiName}
>
<span role="img" aria-label={emoji.name} id={emoji.name}>{emoji.emoji}</span>
</button>
</li>
))
}
</ul>
</div>
)
}
export default App;
此代码使用 JSX 语法map()
覆盖emojis
数组并将它们列为<li>
列表项。它还附加onClick
事件以在浏览器中显示表情符号数据。要更详细地探索代码,请查看如何使用 JSX 创建 React 元素,其中包含对 JSX 的详细说明。
保存并关闭文件。您现在可以删除该logo.svg
文件,因为它是模板的一部分并且您不再引用它:
- rm src/logo.svg
现在,更新样式。打开src/App.css
:
- nano src/App.css
将内容替换为以下 CSS 以将元素居中并调整字体:
.container {
display: flex;
flex-direction: column;
align-items: center;
}
button {
font-size: 2em;
border: 0;
padding: 0;
background: none;
cursor: pointer;
}
ul {
display: flex;
padding: 0;
}
li {
margin: 0 20px;
list-style: none;
padding: 0;
}
这用于flex
将主<h1>
元素和列表元素居中。它还删除了默认按钮样式和<li>
样式,以便表情符号排成一行。更多细节可以在How to Create React Elements with JSX 中找到。
保存并退出文件。
在项目的根目录中打开另一个终端窗口。使用以下命令启动项目:
- npm start
命令运行后,您将在 Web 浏览器中看到该项目正在运行http://localhost:3000
。
在您处理项目的整个过程中保持运行。每次保存项目时,浏览器都会自动刷新并显示最新的代码。
您将看到您的项目页面,其中包含您在文件中列出的Hello、World和三个表情符号App.js
:
现在您已经设置了代码,现在可以开始在 React 中组合组件。
步骤 2 — 使用 React 类创建独立组件
现在您的项目正在运行,您可以开始制作自定义组件。在这一步中,您将通过扩展基础 ReactComponent
类来创建一个独立的 React 组件。您将创建一个新类、添加方法并使用渲染函数来显示数据。
React 组件是可以在整个页面中重复使用的自包含元素。通过编写小的、集中的代码段,您可以随着应用程序的增长移动和重用这些代码段。这里的关键是它们是独立的和专注的,允许您将代码分成逻辑部分。事实上,您已经在使用逻辑分离的组件:该App.js
文件是一个功能组件,您将在步骤 3 中看到更多。
有两种类型的自定义组件:基于类的和功能的。您将要制作的第一个组件是基于类的组件。您将创建一个名为的新组件Instructions
,用于解释表情符号查看器的说明。
注意:基于类的组件曾经是创建 React 组件最流行的方式。但是随着React Hooks的引入,许多开发人员和库正在转向使用函数式组件。
尽管函数式组件现在已成为常态,但您经常会在遗留代码中找到类组件。您不需要使用它们,但您确实需要知道如何识别它们。他们还清楚地介绍了许多未来的概念,例如状态管理。在本教程中,您将学习制作类和功能组件。
首先,创建一个新文件。按照惯例,组件文件大写:
- touch src/Instructions.js
然后在文本编辑器中打开文件:
- nano src/Instructions.js
首先,进口React
和Component
类和出口Instructions
具有以下行:
import React, { Component } from 'react';
export default class Instructions extends Component {}
导入React
将转换 JSX。Component
是您将扩展以创建组件的基类。为了扩展它,您创建了一个具有组件名称 ( Instructions
)的类,并Component
使用该export
行扩展了基类。您还将export default
在类声明的开头使用关键字将此类作为默认值导出。
类名应该大写,并且应该与文件名匹配。这在使用调试工具时很重要,它会显示组件的名称。如果名称与文件结构匹配,将更容易找到相关组件。
基Component
类有多种方法可以在自定义类中使用。最重要的方法,也是您将在本教程中使用的唯一render()
方法,就是方法。该render()
方法返回要在浏览器中显示的 JSX 代码。
首先,在<p>
标签中添加对应用程序的一些解释:
import React, { Component } from 'react';
export class Instructions extends Component {
render() {
return(
<p>Click on an emoji to view the emoji short name.</p>
)
}
}
保存并关闭文件。此时,您的浏览器仍然没有变化。那是因为您还没有使用新组件。要使用该组件,您必须将其添加到连接到根组件的另一个组件中。在这个项目中,<App>
是index.js
. 要使其出现在您的应用程序中,您需要将其添加到<App>
组件中。
src/App.js
在文本编辑器中打开:
- nano src/App.js
首先,您需要导入组件:
import React from 'react';
import Instructions from './Instructions';
import './App.css';
...
export default App;
由于它是默认导入,您可以导入任何您想要的名称。为了便于阅读,最好使名称保持一致——导入应与组件名称相匹配,而组件名称应与文件名相匹配——但唯一的固定规则是组件必须以大写字母开头。这就是 React 知道它是 React 组件的方式。
现在您已经导入了组件,将它添加到您的其余代码中,就像它是一个自定义 HTML 元素一样:
import React from 'react';
import Instructions from './Instructions.js'
...
function App() {
const greeting = "greeting";
const displayAction = false;
return(
<div className="container">
<h1 id={greeting}>Hello, World</h1>
{displayAction && <p>I am writing JSX</p>}
<Instructions />
<ul>
{
emojis.map(emoji => (
<li key={emoji.name}>
<button
onClick={displayEmojiName}
>
<span role="img" aria-label={emoji.name} id={emoji.name}>{emoji.emoji}</span>
</button>
</li>
))
}
</ul>
</div>
)
}
export default App;
在此代码中,您使用尖括号包裹了组件。由于此组件没有任何子组件,因此它可以通过以/>
.
保存文件。当您这样做时,页面将刷新,您将看到新组件。
现在您有一些文本,您可以添加图像。从维基媒体下载表情符号图像并将其保存在src
目录中,emoji.svg
使用以下命令:
- curl -o src/emoji.svg https://upload.wikimedia.org/wikipedia/commons/3/33/Twemoji_1f602.svg
curl
向 URL 发出请求,该-o
标志允许您将文件另存为src/emoji.svg
.
接下来,打开您的组件文件:
- nano src/Instructions.js
导入表情符号并使用动态链接将其添加到您的自定义组件中:
import React, { Component } from 'react';
import emoji from './emoji.svg'
export default class Instructions extends Component {
render() {
return(
<>
<img alt="laughing crying emoji" src={emoji} />
<p>Click on an emoji to view the emoji short name.</p>
</>
)
}
}
请注意,.svg
导入时需要包含文件扩展名。当您导入时,您正在导入一个由webpack在代码编译时创建的动态路径。有关更多信息,请参阅如何使用 Create React App 设置 React 项目。
您还需要用空标签包装<img>
和<p>
标签以确保您返回单个元素。
保存文件。当您重新加载时,与其余内容相比,图像将非常大:
要缩小图像,您需要向className
自定义组件添加一些 CSS 和 a 。
首先,在 中Instructions.js
,将空标签更改为 div 并给它一个className
of instructions
:
import React, { Component } from 'react';
import emoji from './emoji.svg'
export default class Instructions extends Component {
render() {
return(
<div className="instructions">
<img alt="laughing crying emoji" src={emoji} />
<p>Click on an emoji to view the emoji short name.</p>
</div>
)
}
}
保存并关闭文件。下一个打开App.css
:
- nano src/App.css
为.instructions
类选择器创建规则:
.container {
display: flex;
flex-direction: column;
align-items: center;
}
...
.instructions {
display: flex;
flex-direction: column;
align-items: center;
}
当您添加 a display
offlex
样式时,您将img
和p
以flexbox 居中。您更改了方向,使所有内容都与 垂直对齐flex-direction: column;
。该线align-items: center;
将使元素在屏幕上居中。
现在您的元素已排列好,您需要更改图像大小。给img
里面的div
awidth
和height
of 100px
。
.container {
display: flex;
flex-direction: column;
align-items: center;
}
...
.instructions {
display: flex;
flex-direction: column;
align-items: center;
}
.instructions img {
width: 100px;
height: 100px;
}
保存并关闭文件。浏览器将重新加载,您会看到图像小得多:
此时,您已经创建了一个独立且可重用的自定义组件。要查看它的可重用性,请将第二个实例添加到App.js
.
打开App.js
:
- nano src/App.js
在 中App.js
,添加组件的第二个实例:
import React from 'react';
import Instructions from './Instructions.js'
...
function App() {
const greeting = "greeting";
const displayAction = false;
return(
<div className="container">
<h1 id={greeting}>Hello, World</h1>
{displayAction && <p>I am writing JSX</p>}
<Instructions />
<Instructions />
<ul>
{
emojis.map(emoji => (
<li key={emoji.name}>
<button
onClick={displayEmojiName}
>
<span role="img" aria-label={emoji.name} id={emoji.name}>{emoji.emoji}</span>
</button>
</li>
))
}
</ul>
</div>
)
}
export default App;
保存文件。当浏览器重新加载时,您将看到该组件两次。
在这种情况下,您不需要 的两个实例Instructions
,但您可以看到该组件可以有效地重用。当您创建自定义按钮或表格时,您可能会在一个页面上多次使用它们,使它们非常适合自定义组件。
现在,您可以删除额外的图像标签。在您的文本编辑器中,删除第二个<Instructions />
并保存文件:
import React from 'react';
import Instructions from './Instructions.js'
...
function App() {
const greeting = "greeting";
const displayAction = false;
return(
<div className="container">
<h1 id={greeting}>Hello, World</h1>
{displayAction && <p>I am writing JSX</p>}
<Instructions />
<ul>
{
emojis.map(emoji => (
<li key={emoji.name}>
<button
onClick={displayEmojiName}
>
<span role="img" aria-label={emoji.name} id={emoji.name}>{emoji.emoji}</span>
</button>
</li>
))
}
</ul>
</div>
)
}
export default App;
现在您拥有了一个可重复使用的独立组件,您可以多次将其添加到父组件中。您现在拥有的结构适用于少量组件,但存在一个小问题。所有文件混合在一起。的图像<Instructions>
与 的资产位于同一目录中<App>
。您还将 的 CSS 代码<App>
与<Instructions>
.
在下一步中,您将创建一个文件结构,通过将每个组件的功能、样式和依赖项组合在一起来赋予每个组件独立性,使您能够根据需要移动它们。
步骤 3 — 创建可读的文件结构
在此步骤中,您将创建一个文件结构来组织您的组件及其资产,例如图像、CSS 和其他 JavaScript 文件。您将按组件而不是资产类型对代码进行分组。换句话说,您不会有单独的 CSS、图像和 JavaScript 目录。相反,您将为每个包含相关 CSS、JavaScript 和图像的组件创建一个单独的目录。在这两种情况下,您都在分离关注点。
由于您有一个独立的组件,因此您需要一个将相关代码分组的文件结构。目前,所有内容都在同一目录中。列出目录中的项目src
:
- ls src/
输出将显示事情变得非常混乱:
OutputApp.css Instructions.js index.js
App.js emoji.svg serviceWorker.js
App.test.js index.css setupTests.js
您将<App>
组件(App.css
、、App.js
和App.test.js
)的代码与根组件(index.css
和index.js
)和自定义组件放在一起Instructions.js
。
React故意不了解文件结构。它不推荐特定的结构,并且该项目可以使用各种不同的文件层次结构。但我们建议添加一些顺序,以避免组件、CSS 文件和难以导航的图像使您的根目录过载。此外,显式命名可以更容易地查看项目的哪些部分是相关的。例如,名为的图像文件Logo.svg
可能不明确是名为Header.js
.
最简单的结构之一是components
为每个组件创建一个具有单独目录的目录。这将允许您将组件与配置代码分开serviceWorker
分组,例如,同时将资产与组件分组。
创建Components
目录
首先,创建一个名为components
:
- mkdir src/components
接下来,将以下组件和代码到该目录:App.css
,App.js
,App.test.js
,Instructions.js
,和emoji.svg
:
- mv src/App.* src/components/
- mv src/Instructions.js src/components/
- mv src/emoji.svg src/components/
在这里,您使用通配符 ( *
)来选择所有以App.
.
移动代码后,您将在终端中看到运行npm start
.
OutputFailed to compile.
./src/App.js
Error: ENOENT: no such file or directory, open 'your_file_path/tutorial-03-component/src/App.js'
请记住,所有代码都是使用相对路径导入的。如果更改某些文件的路径,则需要更新代码。
为此,请打开index.js
.
- nano src/index.js
然后将App
导入的路径改为从components/
目录中导入。
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App';
import * as serviceWorker from './serviceWorker';
...
serviceWorker.unregister();
保存并关闭文件。您的脚本将检测更改并且错误将消失。
现在您在一个单独的目录中有组件。随着您的应用程序变得越来越复杂,您可能拥有 API 服务、数据存储和实用程序功能的目录。分离组件代码是第一步,但您仍然需要Instructions
在App.css
文件中混合使用 CSS 代码。要创建这种逻辑分离,您首先要将组件移动到单独的目录中。
将组件移动到单个目录
首先,为<App>
组件创建一个专门的目录:
- mkdir src/components/App
然后将相关文件移动到新目录中:
- mv src/components/App.* src/components/App
当你这样做时,你会得到与上一节类似的错误:
OutputFailed to compile.
./src/components/App.js
Error: ENOENT: no such file or directory, open 'your_file_path/tutorial-03-component/src/components/App.js'
在这种情况下,您需要更新两件事。首先,您需要更新index.js
.
打开index.js
文件:
- nano src/index.js
然后更新 App 的导入路径,使其指向目录中的App
组件App
。
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App/App';
import * as serviceWorker from './serviceWorker';
...
serviceWorker.unregister();
保存并关闭文件。应用程序仍然不会运行。你会看到这样的错误:
OutputFailed to compile.
./src/components/App/App.js
Module not found: Can't resolve './Instructions.js' in 'your_file_path/tutorial-03-component/src/components/App'
由于<Instructions>
与<App>
组件不在同一目录级别,因此您需要更改导入路径。在此之前,为Instructions
. 在目录Instructions
中创建一个src/components
目录:
- mkdir src/components/Instructions
然后移动Instructions.js
并emoji.svg
进入该目录:
- mv src/components/Instructions.js src/components/Instructions
- mv src/components/emoji.svg src/components/Instructions
现在Instructions
已经创建了组件目录,您可以完成更新文件路径以将您的组件连接到您的应用程序。
更新import
路径
现在组件位于单独的目录中,您可以在App.js
.
打开App.js
:
- nano src/components/App/App.js
由于路径是相对的,因此您需要向上移动一个目录src/components
——然后进入Instructions
for 目录Instructions.js
,但由于这是一个 JavaScript 文件,因此您不需要最后的导入。
import React from 'react';
import Instructions from '../Instructions/Instructions.js';
import './App.css';
...
export default App;
保存并关闭文件。现在您的导入都使用了正确的路径,您的浏览器将更新并显示应用程序。
注意:也可以调用每个目录下的根文件index.js
。例如,src/components/App/App.js
您可以创建src/components/App/index.js
. 这样做的好处是您的进口量略小。如果路径指向目录,则导入将查找index.js
文件。对进口src/components/App/index.js
中src/index.js
的文件会import ./components/App
。这种方法的缺点是您有很多同名的文件,这会导致在某些文本编辑器中难以阅读。最终,这是个人和团队的决定,但最好保持一致。
分离共享文件中的代码
现在每个组件都有自己的目录,但并不是所有的东西都是完全独立的。最后一步是将 CSS 提取Instructions
到一个单独的文件中。
首先,在src/components/Instructions
以下位置创建一个 CSS 文件:
- touch src/components/Instructions/Instructions.css
接下来,在文本编辑器中打开 CSS 文件:
- nano src/components/Instructions/Instructions.css
添加您在前面部分中创建的指令 CSS:
.instructions {
display: flex;
flex-direction: column;
align-items: center;
}
.instructions img {
width: 100px;
height: 100px;
}
保存并关闭文件。接下来,从src/components/App/App.css
.
- nano src/components/App/App.css
删除关于 的行.instructions
。最终文件将如下所示:
.container {
display: flex;
flex-direction: column;
align-items: center;
}
button {
font-size: 2em;
border: 0;
padding: 0;
background: none;
cursor: pointer;
}
ul {
display: flex;
padding: 0;
}
li {
margin: 0 20px;
list-style: none;
padding: 0;
}
保存并关闭文件。最后,将 CSS 导入Instructions.js
:
- nano src/components/Instructions/Instructions.js
使用相对路径导入 CSS:
import React, { Component } from 'react';
import './Instructions.css';
import emoji from './emoji.svg'
export default class Instructions extends Component {
render() {
return(
<div className="instructions">
<img alt="laughing crying emoji" src={emoji} />
<p>Click on an emoji to view the emoji short name.</p>
</div>
)
}
}
保存并关闭文件。您的浏览器窗口将与以前一样,但现在所有文件资产都分组在同一目录中。
现在,最后看看结构。一、src/
目录:
- ls src
您在目录和实用程序文件(例如和 )旁边有根组件index.js
和相关的 CSS :index.css
components/
serviceWorker.js
setupTests.js
Outputcomponents serviceWorker.js
index.css setupTests.js
index.js
接下来,看看里面components
:
- ls src/components
您将看到每个组件的目录:
OutputApp Instructions
如果您查看每个组件的内部,您将看到组件代码、CSS、测试和图像文件(如果存在)。
- ls src/components/App
OutputApp.css App.js App.test.js
- ls src/components/Instructions
OutputInstructions.css Instructions.js emoji.svg
此时,您已经为您的项目创建了一个坚实的结构。您移动了很多代码,但是现在您有了一个结构,它会更容易扩展。
这不是组成结构的唯一方法。某些文件结构可以通过指定将拆分为不同包的目录来利用代码拆分。其他文件结构按路由拆分,并为跨路由使用的组件使用公共目录。
现在,坚持使用不太复杂的方法。随着对另一种结构的需求出现,从简单到复杂总是更容易。在您需要之前从一个复杂的结构开始会使重构变得困难。
现在您已经创建并组织了一个基于类的组件,在下一步中您将创建一个功能组件。
第 4 步 – 构建功能组件
在此步骤中,您将创建一个功能组件。函数式组件是当代 React 代码中最常见的组件。这些组件往往更短,并且与基于类的组件不同,它们可以使用React hooks,这是一种新的状态和事件管理形式。
函数式组件是返回一些 JSX 的 JavaScript 函数。它不需要扩展任何东西,也没有特殊的方法来记忆。
要将其重构<Instructions>
为函数式组件,您需要将类更改为函数并删除 render 方法,以便只剩下 return 语句。
为此,首先Instructions.js
在文本编辑器中打开。
- nano src/components/Instructions/Instructions.js
将class
声明更改为function
声明:
import React, { Component } from 'react';
import './Instructions.css';
import emoji from './emoji.svg'
export default function Instructions() {
render() {
return(
<div className="instructions">
<img alt="laughing crying emoji" src={emoji} />
<p>Click on an emoji to view the emoji short name.</p>
</div>
)
}
}
接下来,删除导入{ Component }
:
import React from 'react';
import './Instructions.css';
import emoji from './emoji.svg'
export default function Instructions() {
render() {
return(
<div className="instructions">
<img alt="laughing crying emoji" src={emoji} />
<p>Click on an emoji to view the emoji short name.</p>
</div>
)
}
}
最后,删除render()
方法。那时,您只返回 JSX。
import React from 'react';
import './Instructions.css';
import emoji from './emoji.svg'
export default function Instructions() {
return(
<div className="instructions">
<img alt="laughing crying emoji" src={emoji} />
<p>Click on an emoji to view the emoji short name.</p>
</div>
)
}
保存文件。浏览器将刷新,您将看到与以前一样的页面。
You could also rewrite the function as an arrow function using the implicit return. The main difference is that you lose the function body. You will also need to first assign the function to a variable and then export the variable:
import React from 'react';
import './Instructions.css';
import emoji from './emoji.svg'
const Instructions = () => (
<div className="instructions">
<img alt="laughing crying emoji" src={emoji} />
<p>Click on an emoji to view the emoji short name.</p>
</div>
)
export default Instructions;
Simple functional components and class-based components are very similar. When you have a simple component that doesn’t store state, it’s best to use a functional component. The real difference between the two is how you store a component’s state and use properties. Class-based components use methods and properties to set state and tend to be a little longer. Functional components use hooks to store state or manage changes and tend to be a little shorter.
Conclusion
现在您有一个带有独立部分的小应用程序。您创建了两种主要类型的组件:功能组件和类组件。您将组件的各个部分分离到目录中,以便您可以将相似的代码组合在一起。您还导入并重用了这些组件。
了解组件后,您就可以开始将应用程序视为可以拆开并重新组合在一起的部分。项目变得模块化和可互换。将整个应用程序视为一系列组件的能力是在 React 中思考的重要一步。如果您想查看更多 React 教程,请查看我们的React 主题页面,或返回如何在 React.js 系列中编写代码页面。