作者选择Creative Commons接受捐赠,作为Write for DOnations计划的一部分。
介绍
在本教程中,您将通过向组件传递props来创建自定义组件。道具是您提供给JSX 元素的参数。它们看起来像标准的 HTML 道具,但它们不是预定义的,可以有许多不同的JavaScript 数据类型,包括数字、字符串、函数、数组,甚至其他React 组件。您的自定义组件可以使用 props 来显示数据或使用数据使组件具有交互性。道具是创建可适应不同情况的组件的关键部分,了解它们将为您提供开发可处理独特情况的自定义组件的工具。
向组件添加 props 后,您将使用PropTypes
来定义期望组件接收的数据类型。PropTypes
是一个简单的类型系统,用于在运行时检查数据是否与预期类型匹配。它们既是文档又是错误检查器,有助于在扩展时保持应用程序的可预测性。
在本教程结束时,您将使用各种方法props
构建一个小型应用程序,该应用程序将获取一系列动物数据并显示信息,包括名称、科学名称、大小、饮食和其他信息。
注意:第一步设置一个空白项目,您将在该项目上构建教程练习。如果您已经有一个工作项目并想直接使用 props,请从第 2 步开始。
先决条件
-
你需要一个运行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 安装应用程序的说明。本教程还假定您了解 React 组件,您可以在我们的如何在 React教程中创建自定义组件中了解这些知识。
-
您还需要了解 JavaScript 的基础知识(可以在How To Code in JavaScript 中找到),以及 HTML 和 CSS 的基本知识。关于 HTML 和 CSS 的一个很好的资源是Mozilla Developer Network。
第 1 步 – 创建一个空项目
在这一步中,您将使用Create React App创建一个新项目。然后,您将删除引导项目时安装的示例项目和相关文件。最后,您将创建一个简单的文件结构来组织您的组件。
首先,创建一个新项目。在命令行中,运行以下脚本以使用以下命令安装新项目create-react-app
:
- npx create-react-app prop-tutorial
项目完成后,切换到目录:
- cd prop-tutorial
在新的终端选项卡或窗口中,使用Create React App start script启动项目。浏览器将自动刷新更改,因此请在您工作的整个时间运行此脚本:
- npm start
您将获得一个正在运行的本地服务器。如果项目未在浏览器窗口中打开,您可以通过导航到 将其打开http://localhost:3000/
。如果您从远程服务器运行它,则地址将为.http://your_domain:3000
您的浏览器将加载一个简单的 React 应用程序,该应用程序包含在 Create React App 中:
您将构建一套全新的自定义组件。您将首先清除一些样板代码,以便您可以拥有一个空项目。
首先,src/App.js
在文本编辑器中打开。这是注入页面的根组件。所有组件将从这里开始。您可以App.js
在如何使用 Create React App 设置 React 项目中找到更多信息。
src/App.js
使用以下命令打开:
- nano src/App.js
你会看到一个这样的文件:
import React from 'react';
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
删除该行import logo from './logo.svg';
。然后替换return
语句中的所有内容以返回一组空标签:<></>
。这将为您提供一个不返回任何内容的验证页面。最终代码将如下所示:
import React from 'react';
import './App.css';
function App() {
return <></>;
}
export default App;
保存并退出文本编辑器。
最后,删除标志。您不会在您的应用程序中使用它,您应该在工作时删除未使用的文件。它将使您免于将来的困惑。
在终端窗口中输入以下命令:
- rm src/logo.svg
如果您查看浏览器,您将看到一个空白屏幕。
现在您已经清除了示例 Create React App 项目,创建一个简单的文件结构。这将帮助您保持组件隔离和独立。
在目录components
中创建一个名为的src
目录。这将保存您所有的自定义组件。
- mkdir src/components
每个组件都有自己的目录来存储组件文件以及样式、图像(如果有的话)和测试。
创建一个目录App
:
- mkdir src/components/App
将所有App
文件移动到该目录中。使用通配符 ,*
选择以不App.
考虑文件扩展名开头的任何文件。然后使用mv
命令将它们放入新目录中。
- mv src/App.* src/components/App
最后,更新 中的相对导入路径index.js
,它是引导整个过程的根组件。
- nano src/index.js
import 语句需要指向目录App.js
中的App
文件,因此进行以下突出显示的更改:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App/App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
保存并退出文件。
现在项目已设置,您可以创建您的第一个组件。
第 2 步 – 使用 props 构建动态组件
在这一步中,您将创建一个组件,该组件将根据名为props的输入信息进行更改。Props 是您传递给函数或类的参数,但是由于您的组件使用 JSX 转换为类似 HTML 的对象,因此您将像传递 HTML 属性一样传递 props。与 HTML 元素不同,您可以传递许多不同的数据类型,从字符串到数组、对象,甚至函数。
在这里,您将创建一个显示动物信息的组件。该组件将动物的名称和学名作为字符串,将大小作为整数,将饮食作为字符串数组,并将附加信息作为对象。您将信息作为 props 传递给新组件,并在您的组件中使用该信息。
在这一步结束时,您将拥有一个使用不同道具的自定义组件。您还将重用该组件以使用公共组件显示数据数组。
添加数据
首先,您需要一些示例数据。在src/App
名为 data的目录中创建一个文件。
- touch src/components/App/data.js
在文本编辑器中打开新文件:
- nano src/components/App/data.js
接下来,添加一个您将用作示例数据的对象数组:
export default [
{
name: 'Lion',
scientificName: 'Panthero leo',
size: 140,
diet: ['meat'],
},
{
name: 'Gorilla',
scientificName: 'Gorilla beringei',
size: 205,
diet: ['plants', 'insects'],
additional: {
notes: 'This is the eastern gorilla. There is also a western gorilla that is a different species.'
}
},
{
name: 'Zebra',
scientificName: 'Equus quagga',
size: 322,
diet: ['plants'],
additional: {
notes: 'There are three different species of zebra.',
link: 'https://en.wikipedia.org/wiki/Zebra'
}
}
]
对象数组包含各种数据,将让您有机会尝试各种道具。每个对象都是一个单独的动物,带有动物名称、科学名称、大小、饮食和一个名为 的可选字段additional
,其中将包含链接或注释。在此代码中,您还将数组导出为default
.
保存并退出文件。
创建组件
接下来,创建一个名为 的占位符组件AnimalCard
。该组件最终将获取 props 并显示数据。
首先,创建一个src/components
名为的目录,AnimalCard
然后创建一个名为touch
的文件src/components/AnimalCard/AnimalCard.js
和一个名为 .css 的 CSS 文件src/components/AnimalCard/AnimalCard.css
。
- mkdir src/components/AnimalCard
- touch src/components/AnimalCard/AnimalCard.js
- touch src/components/AnimalCard/AnimalCard.css
AnimalCard.js
在文本编辑器中打开:
- nano src/components/AnimalCard/AnimalCard.js
添加一个导入 CSS 并返回<h2>
标签的基本组件。
import React from 'react';
import './AnimalCard.css'
export default function AnimalCard() {
return <h2>Animal</h2>
}
保存并退出文件。现在您需要将数据和组件导入到您的基础App
组件中。
打开src/components/App/App.js
:
- nano src/components/App/App.js
导入数据和组件,然后循环返回数组中每个项目的组件的数据:
import React from 'react';
import data from './data';
import AnimalCard from '../AnimalCard/AnimalCard';
import './App.css';
function App() {
return (
<div className="wrapper">
<h1>Animals</h1>
{data.map(animal => (
<AnimalCard key={animal.name}/>
))}
</div>
)
}
export default App;
保存并退出文件。在这里,您使用.map()
数组方法迭代数据。除了添加此循环之外,您还div
可以使用用于样式化的类和用于<h1>
标记项目的标签进行包装。
保存后,浏览器将重新加载,您将看到每张卡片的标签。
接下来,添加一些样式以排列项目。打开App.css
:
- nano src/components/App/App.css
将内容替换为以下内容以排列元素:
.wrapper {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
padding: 20px;
}
.wrapper h1 {
text-align: center;
width: 100%;
}
这将使用flexbox重新排列数据,使其对齐。这padding
在浏览器窗口中提供了一些空间。justify-content
将分散元素之间的额外空间,并.wrapper h1
为Animal
标签提供全宽。
保存并退出文件。当你这样做时,浏览器将刷新,你会看到一些数据间隔。
添加道具
现在您已经设置了组件,您可以添加您的第一个道具。当您遍历数据时,您可以访问data
数组中的每个对象及其包含的项目。您将把每条数据添加到一个单独的道具中,然后您将在您的AnimalCard
组件中使用它。
打开App.js
:
- nano src/components/App/App.js
添加一个name
to的道具AnimalCard
。
import React from 'react';
...
function App() {
return (
<div className="wrapper">
<h1>Animals</h1>
{data.map(animal => (
<AnimalCard
key={animal.name}
name={animal.name}
/>
))}
</div>
)
}
export default App;
保存并退出文件。该name
道具看起来像一个标准的HTML属性,但不是一个字符串,你通过name
从属性animal
在大括号中的对象。
现在您已经将一个 prop 传递给新组件,您需要使用它。打开AnimalCard.js
:
- nano src/components/AnimalCard/AnimalCard.js
您传递给组件的所有道具都被收集到一个对象中,该对象将成为您函数的第一个参数。解构物体以拉出单个道具:
import React from 'react';
import './AnimalCard.css'
export default function AnimalCard(props) {
const { name } = props;
return (
<h2>{name}</h2>
);
}
请注意,您无需解构 prop 即可使用它,但这是处理本教程中的示例数据的有用方法。
解构对象后,您可以使用各个数据片段。在这种情况下,您将在<h2>
标签中使用标题,并用花括号将值括起来,以便 React 知道将其评估为 JavaScript。
您还可以prop
使用点表示法在对象上使用属性。举个例子,你可以创建一个<h2>
这样的元素:<h2>{props.title}</h2>
。解构的好处是你可以收集未使用的 props 并使用object rest operator。
保存并退出文件。当您这样做时,浏览器将重新加载,您将看到每个动物的特定名称而不是占位符。
该name
属性是一个字符串,但 props 可以是您可以传递给 JavaScript 函数的任何数据类型。要在工作中看到这一点,请添加其余数据。
打开App.js
文件:
- nano src/components/App/App.js
添加道具每个如下:scientificName
,size
,diet
,和additional
。这些包括字符串、整数、数组和对象。
import React from 'react';
...
function App() {
return (
<div className="wrapper">
<h1>Animals</h1>
{albums.map(album => (
<AnimalCard
additional={animal.additional}
diet={animal.diet}
key={animal.name}
name={animal.name}
scientificName={animal.scientificName}
size={animal.size}
/>
))}
</div>
)
}
export default App;
由于您正在创建一个对象,因此您可以按您想要的任何顺序添加它们。按字母顺序排列可以更轻松地浏览道具列表,尤其是在更大的列表中。您也可以将它们添加到同一行中,但将它们分隔为每行一个以保持可读性。
保存并关闭文件。打开AnimalCard.js
。
- nano src/components/AnimalCard/AnimalCard.js
这一次,解构函数参数列表中的props,使用组件中的数据:
import React from 'react';
import './AnimalCard.css'
export default function AnimalCard({
additional,
diet,
name,
scientificName,
size
}) {
return (
<div>
<h2>{name}</h2>
<h3>{scientificName}</h3>
<h4>{size}kg</h4>
<div>{diet.join(', ')}.</div>
</div>
);
}
取出数据后,您可以将scientificName
和添加size
到标题标签中,但您需要将数组转换为字符串,以便 React 可以在页面上显示它。你可以用 来做到这一点join(', ')
,这将创建一个逗号分隔的列表。
保存并关闭文件。当您这样做时,浏览器将刷新,您将看到结构化数据。
您可以使用该additional
对象创建一个类似的列表,但添加一个函数来提醒用户数据。这将使您有机会将函数作为 props 传递,然后在调用函数时使用组件内的数据。
打开App.js
:
- nano src/components/App/App.js
创建一个被调用的函数showAdditionalData
,该函数将对象转换为字符串并将其显示为警报。
import React from 'react';
...
function showAdditional(additional) {
const alertInformation = Object.entries(additional)
.map(information => `${information[0]}: ${information[1]}`)
.join('\n');
alert(alertInformation)
};
function App() {
return (
<div className="wrapper">
<h1>Animals</h1>
{data.map(animal => (
<AnimalCard
additional={animal.additional}
diet={animal.diet}
key={animal.name}
name={animal.name}
scientificName={animal.scientificName}
showAdditional={showAdditional}
size={animal.size}
/>
))}
</div>
)
}
export default App;
该函数showAdditional
将对象转换为一对数组,其中第一项是键,第二项是值。然后映射将密钥对转换为字符串的数据。然后它用换行符将它们连接起来\n
——在将完整的字符串传递给警报函数之前。
由于 JavaScript 可以接受函数作为参数,React 也可以接受函数作为 props。因此,您可以将showAdditional
toAnimalCard
作为一个名为showAdditional
.
保存并关闭文件。打开AnimalCard
:
- nano src/components/AnimalCard/AnimalCard.js
showAdditional
从 props 对象中拉出函数,然后创建一个<button>
带有onClick
事件的事件,该事件使用该additional
对象调用该函数:
import React from 'react';
import './AnimalCard.css'
export default function AnimalCard({
additional,
diet,
name,
scientificName,
showAdditional,
size
}) {
return (
<div>
<h2>{name}</h2>
<h3>{scientificName}</h3>
<h4>{size}kg</h4>
<div>{diet.join(', ')}.</div>
<button onClick={() => showAdditional(additional)}>More Info</button>
</div>
);
}
保存文件。当您这样做时,浏览器将刷新,您将在每张卡片后看到一个按钮。当您单击该按钮时,您将收到带有附加数据的警报。
如果你尝试一下更多信息的Lion
,你会得到一个错误。那是因为没有狮子的额外数据。您将在第 3 步中看到如何解决该问题。
最后,为音乐卡添加一些样式。添加一个className
ofanimal-wrapper
到 div 中AnimalCard
:
import React from 'react';
import './AnimalCard.css'
export default function AnimalCard({
...
return (
<div className="animal-wrapper">
...
</div>
)
}
保存并关闭文件。打开AnimalCard.css
:
- nano src/components/AnimalCard/AnimalCard.css
添加 CSS 为卡片和按钮提供小边框和填充:
.animal-wrapper {
border: solid black 1px;
margin: 10px;
padding: 10px;
width: 200px;
}
.animal-wrapper button {
font-size: 1em;
border: solid black 1px;
padding: 10;
background: none;
cursor: pointer;
margin: 10px 0;
}
此 CSS 将为卡片添加一个轻微的边框,并用边框和填充替换默认按钮样式。cursor: pointer
当您将鼠标悬停在按钮上时,将更改光标。
保存并关闭文件。当您执行此操作时,浏览器将刷新,您将看到各个卡片中的数据。
此时,您已经创建了两个自定义组件。您已经使用 props 将数据从第一个组件传递到第二个组件。props 包括各种数据,例如字符串、整数、数组、对象和函数。在您的第二个组件中,您使用 props 使用 JSX 创建了一个动态组件。
在下一步中,您将使用一个名为的类型系统prop-types
来指定您的组件希望看到的结构,这将在您的应用程序中创建可预测性并防止错误。
第 3 步 — 使用PropTypes
和创建可预测的道具defaultProps
在此步骤中,您将使用PropTypes
. PropTypes
通过明确定义您希望为某个道具接收的数据类型,像其他类型系统一样工作。它们还让您有机会在并不总是需要 prop 的情况下定义默认数据。与大多数类型系统不同,它PropTypes
是一个运行时检查,因此如果 props 与类型不匹配,代码仍然会编译,但也会显示控制台错误。
在这一步结束时,您将通过定义每个道具的类型来为您的自定义组件添加可预测性。这将确保下一个处理组件的人对组件所需的数据结构有一个清晰的认识。
该prop-types
软件包作为 Create React App 安装的一部分包含在内,因此要使用它,您只需将其导入到您的组件中即可。
打开AnimalCard.js
:
- nano src/components/AnimalCard/AnimalCard.js
然后PropTypes
从prop-types
以下导入:
import React from 'react';
import PropTypes from 'prop-types';
import './AnimalCard.css'
export default function AnimalCard({
...
}
PropTypes
直接添加到组件函数中。在 JavaScript 中,函数是对象,这意味着您可以使用点语法添加属性。将以下内容添加PropTypes
到AnimalCard.js
:
import React from 'react';
import PropTypes from 'prop-types';
import './AnimalCard.css'
export default function AnimalCard({
...
}
AnimalCard.propTypes = {
additional: PropTypes.shape({
link: PropTypes.string,
notes: PropTypes.string
}),
diet: PropTypes.arrayOf(PropTypes.string).isRequired,
name: PropTypes.string.isRequired,
scientificName: PropTypes.string.isRequired,
showAdditional: PropTypes.func.isRequired,
size: PropTypes.number.isRequired,
}
保存并关闭文件。
如您所见,有许多不同的PropTypes
. 这只是一个小样本;查看官方 React 文档以查看您可以使用的其他文档。
让我们从name
道具开始。在这里,您指定name
必须是string
. 属性scientificName
是一样的。size
是 a number
,它可以包括浮点数(例如 )1.5
和整数(例如 )6
。showAdditional
是一个函数(func
)。
diet
,另一方面,有点不同。在这种情况下,您指定的diet
是array
,但您还需要指定此数组将包含的内容。在这种情况下,数组将只包含字符串。如果你想混合类型,你可以使用另一个名为 的道具oneOfType
,它接受一个有效的数组PropTypes
。您可以oneOfType
在任何地方使用,因此如果您想size
成为数字或字符串,您可以将其更改为:
size: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
道具additional
也有点复杂。在这种情况下,您正在指定一个对象,但更清楚一点,您是在说明您希望该对象包含什么。要做到这一点,您可以使用PropTypes.shape
,它接受一个带有额外字段的对象,这些字段需要自己的PropTypes
. 在这种情况下,link
和notes
都是PropTypes.string
。
目前,所有数据都格式良好并与道具匹配。要查看PropTypes
不匹配时会发生什么,请打开您的数据:
- nano src/components/App/data.js
将第一项的大小更改为字符串:
export default [
{
name: 'Lion',
scientificName: 'Panthero leo',
size: '140',
diet: ['meat'],
},
...
]
保存文件。当您执行此操作时,浏览器将刷新,您将在控制台中看到错误。
Errorindex.js:1 Warning: Failed prop type: Invalid prop `size` of type `string` supplied to `AnimalCard`, expected `number`.
in AnimalCard (at App.js:18)
in App (at src/index.js:9)
in StrictMode (at src/index.js:8)
与TypeScript等其他类型系统不同,PropTypes
在构建时不会给你警告,只要没有代码错误,它仍然会编译。这意味着您可能会意外发布带有 prop 错误的代码。
将数据改回正确的类型:
export default [
{
name: 'Lion',
scientificName: 'Panthero leo',
size: 140,
diet: ['meat'],
},
...
]
保存并关闭文件。
打开AnimalCard.js
:
- nano src/components/AnimalCard/AnimalCard.js
每个道具除了additional
有isRequired
属性。这意味着,它们是必需的。如果您不包含必需的 prop,代码仍会编译,但您会在控制台中看到运行时错误。
如果不需要道具,您可以添加默认值。如果不需要 prop,则始终添加默认值以防止运行时错误是一种很好的做法。例如,在AnimalCard
组件中,您正在使用additional
数据调用函数。如果它不存在,该函数将尝试修改一个不存在的对象,应用程序将崩溃。
为了防止这个问题,添加一个defaultProp
for additional
:
import React from 'react';
import PropTypes from 'prop-types';
import './AnimalCard.css'
export default function AnimalCard({
...
}
AnimalCard.propTypes = {
additional: PropTypes.shape({
link: PropTypes.string,
notes: PropTypes.string
}),
...
}
AnimalCard.defaultProps = {
additional: {
notes: 'No Additional Information'
}
}
您可以像defaultProps
使用 一样使用点语法向函数propTypes
添加 ,然后添加一个默认值,如果 prop 是 ,则组件应使用该默认值undefined
。在这种情况下,您正在匹配 的形状additional
,包括一条消息,表明没有附加信息。
Save and close the file. When you do, the browser will refresh. After it refreshes, click on the More Info button for Lion. It has no additional
field in the data so the prop is undefined
. But AnimalCard
will substitute in the default prop.
Now your props are well-documented and are either required or have a default to ensure predictable code. This will help future developers (including yourself) understand what props a component needs. It will make it easier to swap and reuse your components by giving you full information about how the component will use the data it is receiving.
Conclusion
In this tutorial, you have created several components that use props to display information from a parent. Props give you the flexibility to begin to break larger components into smaller, more focused pieces. Now that you no longer have your data tightly coupled with your display information, you have the ability to make choices about how to segment your application.
Props 是构建复杂应用程序的重要工具,它提供了创建能够适应接收到的数据的组件的机会。使用PropTypes
,您正在创建可预测且可读的组件,这将使团队能够重用彼此的工作以创建灵活且稳定的代码库。如果您想查看更多 React 教程,请查看我们的React 主题页面,或返回如何在 React.js 系列中编写代码页面。