如何使用Props自定义React组件
作为 Write for DOnations 计划的一部分,作者选择了 Creative Commons 来接受捐赠。
介绍
在本教程中,您将通过将 props 传递给您的组件来创建自定义组件。 道具是您提供给 JSX 元素 的参数。 它们看起来像标准的 HTML 道具,但它们不是预定义的,可以有许多不同的 JavaScript 数据类型,包括数字、字符串、函数、数组,甚至其他 React 组件。 您的自定义组件可以使用道具显示数据或使用数据使组件交互。 道具是创建可适应不同情况的组件的关键部分,了解它们将为您提供开发可处理独特情况的自定义组件的工具。
将 props 添加到组件后,您将使用 PropTypes 来定义您希望组件接收的数据类型。 PropTypes 是一个简单的类型系统,用于在运行时检查数据是否与预期类型匹配。 它们既可用作文档,又可用作错误检查器,可帮助您的应用程序在扩展时保持可预测性。
在本教程结束时,您将使用各种 props 构建一个小型应用程序,该应用程序将获取一组动物数据并显示信息,包括名称、学名、大小、饮食和附加信息。
注意:第一步设置一个空白项目,您将在该项目上构建教程练习。 如果您已经有一个工作项目并想直接使用道具,请从 Step 2 开始。
先决条件
- 您将需要一个运行 Node.js 的开发环境; 本教程在 Node.js 版本 10.20.1 和 npm 版本 6.14.4 上进行了测试。 要在 macOS 或 Ubuntu 18.04 上安装它,请按照 如何在 macOS 上安装 Node.js 和创建本地开发环境中的步骤或 的 使用 PPA 部分安装如何在 Ubuntu 18.04 上安装 Node.js。
- 在本教程之后,您将使用 Create React App。 你可以在 How To Set Up a React Project with Create React App 找到使用 Create React App 安装应用程序的说明。 本教程还假设您了解 React 组件,您可以在我们的 How To Create Custom Components in React 教程中了解这些知识。
- 您还需要了解 JavaScript 的基础知识,您可以在 如何在 JavaScript 中找到这些基础知识,以及 HTML 和 CSS 的基本知识。 HTML 和 CSS 的一个很好的资源是 Mozilla 开发者网络 。
第 1 步——创建一个空项目
在这一步中,您将使用 Create React App 创建一个新项目。 然后,您将删除引导项目时安装的示例项目和相关文件。 最后,您将创建一个简单的文件结构来组织您的组件。
首先,创建一个新项目。 在命令行中,运行以下脚本以使用 create-react-app 安装新项目:
npx create-react-app prop-tutorial
项目完成后,进入目录:
cd prop-tutorial
在新的终端选项卡或窗口中,使用 Create React App 启动脚本 启动项目。 浏览器将自动刷新更改,因此请让此脚本在您工作的整个过程中运行:
npm start
您将获得一个正在运行的本地服务器。 如果项目没有在浏览器窗口中打开,您可以通过导航到 http://localhost:3000/ 来打开它。 如果您从远程服务器运行它,地址将是 http://your_domain:3000。
您的浏览器将加载一个简单的 React 应用程序,该应用程序包含在 Create React App 中:
您将构建一组全新的自定义组件。 您将首先清除一些样板代码,以便您可以拥有一个空项目。
首先,在文本编辑器中打开 src/App.js。 这是注入页面的根组件。 所有组件都将从这里开始。 你可以在 How To Set Up a React Project with Create React App 中找到有关 App.js 的更多信息。
使用以下命令打开 src/App.js:
nano src/App.js
你会看到一个像这样的文件:
道具教程/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 语句中的所有内容以返回一组空标签:<></>。 这将为您提供一个不返回任何内容的验证页面。 最终代码将如下所示:
道具教程/src/App.js
import React from 'react';
import './App.css';
function App() {
return <></>;
}
export default App;
保存并退出文本编辑器。
最后,删除标志。 您不会在应用程序中使用它,您应该在工作时删除未使用的文件。 它将使您免于将来的困惑。
在终端窗口中键入以下命令:
rm src/logo.svg
如果您查看浏览器,您将看到一个空白屏幕。
现在您已经清除了示例 Create React App 项目,创建一个简单的文件结构。 这将帮助您保持组件的独立性和独立性。
在 src 目录下创建一个名为 components 的目录。 这将包含您所有的自定义组件。
mkdir src/components
每个组件都有自己的目录来存储组件文件以及样式、图像(如果有的话)和测试。
为 App 创建一个目录:
mkdir src/components/App
将所有 App 文件移动到该目录中。 使用通配符 * 选择以 App. 开头的任何文件,无论文件扩展名如何。 然后使用 mv 命令将它们放入新目录。
mv src/App.* src/components/App
最后,更新index.js中的相对导入路径,这是引导整个过程的根组件。
nano src/index.js
导入语句需要指向App目录下的App.js文件,所以做如下高亮修改:
道具教程/src/index.js
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 是您传递给函数或类的参数,但是由于您的组件使用 JSX 转换为类似 HTML 的对象,因此您将传递 props 就像它们是 HTML 属性一样。 与 HTML 元素不同,您可以传递许多不同的数据类型,从字符串到数组、到对象,甚至是函数。
在这里,您将创建一个显示动物信息的组件。 该组件将动物的名称和学名作为字符串,将大小作为整数,将饮食作为字符串数组,并将附加信息作为对象。 您将信息作为道具传递给新组件,并在您的组件中使用该信息。
在这一步结束时,您将拥有一个使用不同道具的自定义组件。 您还将重用该组件以使用通用组件显示数据数组。
添加数据
首先,您需要一些样本数据。 在 src/App 目录中创建一个名为 data 的文件。
touch src/components/App/data.js
在文本编辑器中打开新文件:
nano src/components/App/data.js
接下来,添加将用作示例数据的 objects 数组:
道具教程/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 的占位符组件。 该组件最终将获取道具并显示数据。
首先,在 src/components 中创建一个名为 AnimalCard 的目录,然后在 touch 中创建一个名为 src/components/AnimalCard/AnimalCard.js 的文件和一个名为 src/components/AnimalCard/AnimalCard.css 的 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> 标记的基本组件。
道具教程/src/components/AnimalCard/AnimalCard.js
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
导入数据和组件,然后循环返回数组中每个项目的组件的数据:
道具教程/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
将内容替换为以下内容以排列元素:
道具教程/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 属性添加到 AnimalCard。
道具教程/src/components/App/App.js
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 属性,但不是字符串,而是花括号中 animal 对象中的 name 属性。
现在您已经将一个 prop 传递给新组件,您需要使用它。 打开AnimalCard.js:
nano src/components/AnimalCard/AnimalCard.js
您传递给组件的所有道具都被收集到一个对象中,该对象将成为您的函数的第一个参数。 Destructure 物体拉出单个道具:
道具教程/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>。 destructring 的优点是可以收集未使用的 props 并使用 对象休息运算符 。
保存并退出文件。 当您这样做时,浏览器将重新加载,您将看到每只动物的特定名称,而不是占位符。
name 属性是一个字符串,但 props 可以是您可以传递给 JavaScript 函数的任何数据类型。 要在工作中看到这一点,请添加其余数据。
打开App.js文件:
nano src/components/App/App.js
为以下每个添加一个道具:scientificName、size、diet 和 additional。 这些包括字符串、整数、数组和对象。
道具教程/src/components/App/App.js
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,使用组件中的数据:
道具教程/src/components/AnimalCard/AnimalCard.js
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 对象创建一个类似的列表,而是添加一个函数来提醒用户数据。 这将使您有机会将函数作为道具传递,然后在调用函数时使用组件内的数据。
打开App.js:
nano src/components/App/App.js
创建一个名为 showAdditionalData 的函数,它将对象转换为字符串并将其显示为警报。
道具教程/src/components/App/App.js
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 也可以接受函数作为道具。 因此,您可以将 showAdditional 作为称为 showAdditional 的属性传递给 AnimalCard。
保存并关闭文件。 打开AnimalCard:
nano src/components/AnimalCard/AnimalCard.js
从 props 对象中拉出 showAdditional 函数,然后创建一个带有 onClick 事件 的 <button> ,该事件使用 additional 对象调用该函数:
道具教程/src/components/AnimalCard/AnimalCard.js
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 的 More Info,您将收到错误消息。 那是因为没有狮子的额外数据。 您将在第 3 步中看到如何解决该问题。
最后,为音乐卡添加一些样式。 将 animal-wrapper 的 className 添加到 AnimalCard 的 div 中:
道具教程/src/components/AnimalCard/AnimalCard.js
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 为卡片和按钮提供小边框和填充:
道具教程/src/components/AnimalCard/AnimalCard.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 包括各种数据,例如字符串、整数、数组、对象和函数。 在您的第二个组件中,您使用道具使用 JSX 创建了一个动态组件。
在下一步中,您将使用一个名为 prop-types 的类型系统来指定您的组件期望看到的结构,这将在您的应用程序中创建可预测性并防止错误。
第 3 步 — 使用 PropTypes 和 defaultProps 创建可预测的道具
在此步骤中,您将使用 PropTypes 向组件中添加灯光类型系统。 PropTypes 与其他类型系统一样,通过显式定义您希望为某个道具接收的数据类型来发挥作用。 它们还使您有机会在并不总是需要道具的情况下定义默认数据。 与大多数类型系统不同,PropTypes 是运行时检查,因此如果 props 与类型不匹配,代码仍然可以编译,但也会显示控制台错误。
在这一步结束时,您将通过定义每个道具的类型来为您的自定义组件添加可预测性。 这将确保下一个在组件上工作的人将清楚地了解组件所需的数据结构。
prop-types 包包含在 Create React App 安装中,因此要使用它,您只需将其导入组件即可。
打开AnimalCard.js:
nano src/components/AnimalCard/AnimalCard.js
然后从 prop-types 导入 PropTypes:
道具教程/src/components/AnimalCard/AnimalCard.js
import React from 'react';
import PropTypes from 'prop-types';
import './AnimalCard.css'
export default function AnimalCard({
...
}
将PropTypes直接添加到组件函数中。 在 JavaScript 中, 函数是对象,这意味着您可以使用点语法添加属性。 将以下 PropTypes 添加到 AnimalCard.js:
道具教程/src/components/AnimalCard/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 是 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
将第一项的大小更改为字符串:
道具教程/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 错误的代码。
将数据改回正确的类型:
道具教程/src/components/App/data.js
export default [
{
name: 'Lion',
scientificName: 'Panthero leo',
size: 140,
diet: ['meat'],
},
...
]
保存并关闭文件。
打开AnimalCard.js:
nano src/components/AnimalCard/AnimalCard.js
除了 additional 之外的每个道具都有 isRequired 属性。 这意味着,它们是必需的。 如果您不包含必需的道具,代码仍将编译,但您会在控制台中看到运行时错误。
如果不需要道具,您可以添加默认值。 如果不需要道具,最好始终添加默认值以防止运行时错误。 例如,在 AnimalCard 组件中,您正在使用 additional 数据调用函数。 如果它不存在,该函数将尝试修改一个不存在的对象并且应用程序将崩溃。
为防止出现此问题,请为 additional 添加 defaultProp:
道具教程/src/components/AnimalCard/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
}),
...
}
AnimalCard.defaultProps = {
additional: {
notes: 'No Additional Information'
}
}
就像使用 propTypes 一样,使用点语法将 defaultProps 添加到函数中,然后添加组件应使用的默认值,如果 prop 是 undefined。 在这种情况下,您匹配的是 additional 的形状,包括没有附加信息的消息。
保存并关闭文件。 当你这样做时,浏览器将刷新。 刷新后,单击 Lion 的 More Info 按钮。 它在数据中没有 additional 字段,所以 prop 是 undefined。 但是 AnimalCard 将替换默认道具。
现在,您的 props 有据可查,要么是必需的,要么是默认的,以确保可预测的代码。 这将帮助未来的开发人员(包括您自己)了解组件需要什么道具。 通过为您提供有关组件将如何使用其接收的数据的完整信息,它将使交换和重用组件变得更加容易。
结论
在本教程中,您创建了几个使用 props 显示父级信息的组件。 道具使您可以灵活地开始将较大的组件分解成更小、更集中的部分。 现在您的数据不再与显示信息紧密耦合,您可以选择如何分割应用程序。
Props 是构建复杂应用程序的关键工具,它提供了创建能够适应它们接收到的数据的组件的机会。 使用 PropTypes,您正在创建可预测和可读的组件,这将使团队能够重用彼此的工作以创建灵活且稳定的代码库。 如果您想查看更多 React 教程,请查看我们的 React 主题页面,或返回 如何在 React.js 中编码系列页面。