如何使用 props 自定义 React 组件

作者选择Creative Commons接受捐赠,作为Write for DOnations计划的一部分。

介绍

在本教程中,您将通过向组件传递props创建自定义组件。道具是您提供给JSX 元素的参数它们看起来像标准的 HTML 道具,但它们不是预定义的,可以有许多不同的JavaScript 数据类型,包括数字、字符串、函数数组,甚至其他React 组件您的自定义组件可以使用 props 来显示数据或使用数据使组件具有交互性。道具是创建可适应不同情况的组件的关键部分,了解它们将为您提供开发可处理独特情况的自定义组件的工具。

向组件添加 props 后,您将使用PropTypes来定义期望组件接收的数据类型。PropTypes是一个简单的类型系统,用于在运行时检查数据是否与预期类型匹配。它们既是文档又是错误检查器,有助于在扩展时保持应用程序的可预测性。

在本教程结束时,您将使用各种方法props构建一个小型应用程序,该应用程序将获取一系列动物数据并显示信息,包括名称、科学名称、大小、饮食和其他信息。

注意:第一步设置一个空白项目,您将在该项目上构建教程练习。如果您已经有一个工作项目并想直接使用 props,请从第 2 步开始

先决条件

第 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

你会看到一个这样的文件:

道具教程/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

如果您查看浏览器,您将看到一个空白屏幕。

chrome 中的空白屏幕

现在您已经清除了示例 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文件,因此进行以下突出显示的更改:

道具教程/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的输入信息进行更改Props 是您传递给函数或类的参数,但是由于您的组件使用 JSX 转换为类似 HTML 的对象,因此您将像传递 HTML 属性一样传递 props。与 HTML 元素不同,您可以传递许多不同的数据类型,从字符串到数组、对象,甚至函数。

在这里,您将创建一个显示动物信息的组件。该组件将动物的名称和学名作为字符串,将大小作为整数,将饮食作为字符串数组,并将附加信息作为对象。您将信息作为 props 传递给新组件,并在您的组件中使用该信息。

在这一步结束时,您将拥有一个使用不同道具的自定义组件。您还将重用该组件以使用公共组件显示数据数组。

添加数据

首先,您需要一些示例数据。src/App名为 data目录中创建一个文件

  • touch src/components/App/data.js

在文本编辑器中打开新文件:

  • nano src/components/App/data.js

接下来,添加一个您将用作示例数据对象数组

道具教程/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>标签的基本组件

道具教程/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 h1Animal标签提供全宽。

保存并退出文件。当你这样做时,浏览器将刷新,你会看到一些数据间隔。

在浏览器中反应项目,数据间隔开

添加道具

现在您已经设置了组件,您可以添加您的第一个道具。当您遍历数据时,您可以访问data数组中的每个对象及其包含的项目。您将把每条数据添加到一个单独的道具中,然后您将在您的AnimalCard组件中使用它

打开App.js:

  • nano src/components/App/App.js

添加一个nameto的道具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属性,但不是一个字符串,你通过name从属性animal在大括号中的对象。

现在您已经将一个 prop 传递给新组件,您需要使用它。打开AnimalCard.js:

  • nano src/components/AnimalCard/AnimalCard.js

您传递给组件的所有道具都被收集到一个对象中,该对象将成为您函数的第一个参数。解构物体以拉出单个道具:

道具教程/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

保存并退出文件。当您这样做时,浏览器将重新加载,您将看到每个动物的特定名称而不是占位符。

带有动物名称的 React 项目

name属性是一个字符串,但 props 可以是您可以传递给 JavaScript 函数的任何数据类型。要在工作中看到这一点,请添加其余数据。

打开App.js文件:

  • nano src/components/App/App.js

添加道具每个如下:scientificNamesizediet,和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对象创建一个类似的列表,但添加一个函数来提醒用户数据。这将使您有机会将函数作为 props 传递,然后在调用函数时使用组件内的数据。

打开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 也可以接受函数作为 props。因此,您可以将showAdditionaltoAnimalCard作为一个名为showAdditional.

保存并关闭文件。打开AnimalCard:

  • nano src/components/AnimalCard/AnimalCard.js

showAdditional从 props 对象中拉出函数,然后创建一个<button>带有onClick事件的事件事件使用该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,你会得到一个错误。那是因为没有狮子的额外数据。您将在第 3 步中看到如何解决该问题。

最后,为音乐卡添加一些样式。添加一个classNameofanimal-wrapper到 div 中AnimalCard

道具教程/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 将数据从第一个组件传递到第二个组件。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

然后PropTypesprop-types以下导入

道具教程/src/components/AnimalCard/AnimalCard.js
import React from 'react';
import PropTypes from 'prop-types';
import './AnimalCard.css'

export default function AnimalCard({
...
}

PropTypes直接添加到组件函数中。在 JavaScript 中,函数是对象,这意味着您可以使用点语法添加属性。将以下内容添加PropTypesAnimalCard.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是 a number,它可以包括浮点数(例如 )1.5和整数(例如 )6showAdditional是一个函数(func)。

diet,另一方面,有点不同。在这种情况下,您指定的dietarray,但您还需要指定此数组将包含的内容。在这种情况下,数组将只包含字符串。如果你想混合类型,你可以使用另一个名为 的道具oneOfType,它接受一个有效的数组PropTypes您可以oneOfType在任何地方使用,因此如果您想size成为数字或字符串,您可以将其更改为:

size: PropTypes.oneOfType([PropTypes.number, PropTypes.string])

道具additional也有点复杂。在这种情况下,您正在指定一个对象,但更清楚一点,您是在说明您希望该对象包含什么。要做到这一点,您可以使用PropTypes.shape,它接受一个带有额外字段的对象,这些字段需要自己的PropTypes. 在这种情况下,linknotes都是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'],
  },
...
]

保存文件。当您执行此操作时,浏览器将刷新,您将在控制台中看到错误。

Error
index.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

每个道具除了additionalisRequired属性。这意味着,它们是必需的。如果您不包含必需的 prop,代码仍会编译,但您会在控制台中看到运行时错误。

如果不需要道具,您可以添加默认值。如果不需要 prop,则始终添加默认值以防止运行时错误是一种很好的做法。例如,在AnimalCard组件中,您正在使用additional数据调用函数如果它不存在,该函数将尝试修改一个不存在的对象,应用程序将崩溃。

为了防止这个问题,添加一个defaultPropfor additional

道具教程/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'
  }
}

您可以像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 系列中编写代码页面

觉得文章有用?

点个广告表达一下你的爱意吧 !😁