react中创建一个组件_如何在带有道具的React中创建包装器组件

react中创建一个组件

The author selected Creative Commons to receive a donation as part of the Write for DOnations program.

作者选择了创用CC来接受捐赠,这是Write for DOnations计划的一部分。

介绍 (Introduction)

In this tutorial, you’ll create wrapper components with props using the React JavaScript library. Wrapper components are components that surround unknown components and provide a default structure to display the child components. This pattern is useful for creating user interface (UI) elements that are used repeatedly throughout a design, like modals, template pages, and information tiles.

在本教程中,您将使用React JavaScript库使用props创建包装器组件。 包装器组件是包围未知组件并提供默认结构以显示子组件的组件。 该模式对于创建在整个设计中反复使用的用户界面(UI)元素很有用,例如模式,模板页面和信息图块。

To create wrapper components, you’ll first learn to use the rest and spread operators to collect unused props to pass down to nested components. Then you’ll create a component that uses the built-in children component to wrap nested components in JSX as if they were HTML elements. Finally, you’ll pass components as props to create flexible wrappers that can embed custom JSX in multiple locations in a component.

要创建包装器组件,您首先将学习使用其余操作和散布运算符来收集未使用的道具,以传递给嵌套组件。 然后,您将创建一个使用内置children组件将JSX中的嵌套组件包装为HTML元素的组件。 最后,您将组件作为道具传递来创建灵活的包装器,这些包装器可以将自定义JSX嵌入组件中的多个位置。

During the tutorial, you’ll build components to display a list of animal data in the form of cards. You’ll learn to split data and refactor components as you create flexible wrapping components. By the end of this tutorial, you’ll have a working application that will use advanced prop techniques to create reusable components that will scale and adapt as you application grows and changes.

在本教程中,您将构建组件以显示卡片形式的动物数据列表。 创建灵活的包装组件时,您将学习拆分数据和重构组件。 在本教程结束时,您将拥有一个正常工作的应用程序,该应用程序将使用高级道具技术来创建可重复使用的组件,这些组件将随着应用程序的增长和更改而扩展和适应。

Note: The first step sets up a blank project on which you will build the tutorial exercise. If you already have a working project and want to go directly to working with props, start with Step 2.

注意 :第一步将设置一个空白项目,您将在该项目上构建本教程练习。 如果您已经有一个正在运行的项目,并且想直接使用道具,请从步骤2开始。

先决条件 (Prerequisites)

第1步-创建一个空项目 (Step 1 — Creating an Empty Project)

In this step, you’ll create a new project using Create React App. Then you will delete the sample project and related files that are installed when you bootstrap the project. Finally, you will create a simple file structure to organize your components. This will give you a solid basis on which to build this tutorial’s wrapper application in the next step.

在这一步中,您将使用Create React App创建一个新项目。 然后,您将删除示例项目以及在引导项目时安装的相关文件。 最后,您将创建一个简单的文件结构来组织您的组件。 这将为您在下一步中构建本教程的包装器应用程序提供坚实的基础。

To start, make a new project. In your command line, run the following script to install a fresh project using create-react-app:

首先,创建一个新项目。 在命令行中,运行以下脚本以使用create-react-app安装新项目:

  • npx create-react-app wrapper-tutorial

    npx create-react-app wrapper-tutorial

After the project is finished, change into the directory:

项目完成后,转到目录:

  • cd wrapper-tutorial

    cd wrapper-tutorial

In a new terminal tab or window, start the project using the Create React App start script. The browser will auto-refresh on changes, so leave this script running while you work:

在新的终端标签或窗口中,使用Create React App启动脚本启动项目。 浏览器将自动刷新所做的更改,因此在工作时请保持此脚本运行:

  • npm start

    npm开始

You will get a running local server. If the project did not open in a browser window, you can open it with http://localhost:3000/. If you are running this from a remote server, the address will be http://your_domain:3000.

您将获得正在运行的本地服务器。 如果未在浏览器窗口中打开项目,则可以使用http://localhost:3000/打开它。 如果您是从远程服务器运行的,则该地址将为http:// your_domain :3000

Your browser will load with a simple React application included as part of Create React App:

您的浏览器将加载一个简单的React应用程序,该应用程序是Create React App的一部分:

You will be building a completely new set of custom components, so you’ll need to start by clearing out some boilerplate code so that you can have an empty project.

您将构建一套全新的自定义组件,因此您需要从清除一些样板代码开始,以便拥有一个空项目。

To start, open src/App.js in a text editor. This is the root component that is injected into the page. All components will start from here. You can find more information about App.js at How To Set Up a React Project with Create React App.

首先,在文本编辑器中打开src/App.js 这是注入页面的根组件。 所有组件将从此处开始。 您可以在如何使用创建React App设置React项目中找到有关App.js更多信息。

Open src/App.js with the following command:

使用以下命令打开src/App.js

  • nano src/App.js

    纳米src / App.js

You will see a file like this:

您将看到如下文件:

wrapper-tutorial/src/App.js
wrapper-tutorial / 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;

Delete the line import logo from './logo.svg';. Then replace everything in the return statement to return a set of empty tags: <></>. This will give you a valid page that returns nothing. The final code will look like this:

import logo from './logo.svg';删除行import logo from './logo.svg'; 。 然后替换return语句中的所有内容以返回一组空标签: <></> 。 这将为您提供一个不返回任何内容的有效页面。 最终代码如下所示:

wrapper-tutorial/src/App.js
wrapper-tutorial / src / App.js
import React from 'react';
import './App.css';

function App() {
  return <></>;
}

export default App;

Save and exit the text editor.

保存并退出文本编辑器。

Finally, delete the logo. You won’t be using it in your application and you should remove unused files as you work. It will save you from confusion in the long run.

最后,删除徽标。 您将不会在应用程序中使用它,并且应在工作时删除未使用的文件。 从长远来看,它将使您避免混乱。

In the terminal window type the following command:

在终端窗口中,输入以下命令:

  • rm src/logo.svg

    rm src / logo.svg

If you look at your browser, you will see a blank screen.

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

Now that you have cleared out the sample Create React App project, create a simple file structure. This will help you keep your components isolated and independent.

现在,您已经清除了示例创建React App项目,创建一个简单的文件结构。 这将帮助您使组件保持隔离和独立。

Create a directory called components in the src directory. This will hold all of you custom components.

src目录中创建一个名为components的目录。 这将容纳您所有的自定义组件。

  • mkdir src/components

    mkdir src /组件

Each component will have its own directory to store the component file along with the styles, images if there are any, and tests.

每个组件都有其自己的目录,用于存储组件文件以及样式,图像(如果有)和测试。

Create a directory for App:

App创建目录:

  • mkdir src/components/App

    mkdir src / components / App

Move all of the App files into that directory. Use the wildcard, *, to select any files that start with App. regardless of file extension. Then use the mv command to put them into the new directory:

将所有App文件移到该目录。 使用通配符*来选择以App.开头的任何文件App. 无论文件扩展名如何。 然后使用mv命令将它们放入新目录:

  • mv src/App.* src/components/App

    mv src / App。* src / components / App

Next, update the relative import path in index.js, which is the root component that bootstraps the whole process:

接下来,更新index.js的相对导入路径,它是引导整个过程的根组件:

  • nano src/index.js

    纳米src / index.js

The import statement needs to point to the App.js file in the App directory, so make the following highlighted change:

import语句需要指向App目录中的App.js文件,因此请进行以下突出显示的更改:

wrapper-tutorial/src/index.js
wrapper-tutorial / 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();

Save and exit the file.

保存并退出文件。

Now that the project is set up, you can create your first component.

现在已经建立了项目,您可以创建第一个组件。

第2步-使用...props收集未使用的...props (Step 2 — Collecting Unused Props with ...props)

In this step, you’ll create a component to display a set of data about a group of animals. Your component will contain a second nested component to display some information visually. To connect the parent and nested component, you’ll use the rest and spread operators to pass unused props from the parent to the child without the parent needing to be aware of the names or types of the props.

在此步骤中,您将创建一个组件以显示有关一组动物的一组数据。 您的组件将包含第二个嵌套组件,以可视方式显示一些信息。 要连接父级组件和嵌套组件,您将使用rest和spread运算符将未使用的道具从父级传递给子级,而父级无需知道道具的名称或类型。

By the end of this step, you’ll have a parent component that can provide props to nested components without having to know what the props are. This will keep the parent component flexible, allowing you to update the child component without having to change the parent.

在此步骤结束时,您将拥有一个父组件,该组件可以为嵌套组件提供道具,而不必知道道具是什么。 这将使父组件保持灵活,从而使您无需更改父组件即可更新子组件。

创建一个AnimalCard组件 (Creating an AnimalCard Component)

To start, create a set of data for your animals. First, open a file containing the data set in the components/App directory:

首先,为您的动物创建一组数据。 首先,打开一个文件,其中包含components/App目录中的数据集:

  • nano src/components/App/data.js

    纳米src / components / App / data.js

Add the following data:

添加以下数据:

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']
  },
  {
    name: 'Zebra',
    scientificName: 'Equus quagga',
    size: 322,
    diet: ['plants'],
  }
]

This list of animals is an array of objects that includes the animal’s name, scientific name, weight, and diet.

该动物列表是一系列 对象 ,其中包括动物的名称,学名,体重和饮食。

Save and close the file.

保存并关闭文件。

Next, create a directory for the AnimalCard component:

接下来,为AnimalCard组件创建一个目录:

  • mkdir src/components/AnimalCard

    mkdir src / components / AnimalCard

Open a new file in the directo:

在目录中打开一个新文件:

  • nano src/components/AnimalCard/AnimalCard.js

    纳米src / components / AnimalCard / AnimalCard.js

Now add a component that will take the name, diet, and size as a prop and display it:

现在添加一个将namedietsize作为道具的组件并将其显示:

wrapper-tutorial/src/components/AnimalCard/AnimalCard.js
wrapper-tutorial / src / components / AnimalCard / AnimalCard.js
import React from 'react';
import PropTypes from 'prop-types';

export default function AnimalCard({ diet, name, size }) {
  return(
    <div>
      <h3>{name}</h3>
      <div>{size}kg</div>
      <div>{diet.join(', ')}.</div>
    </div>
  )
}

AnimalCard.propTypes = {
  diet: PropTypes.arrayOf(PropTypes.string).isRequired,
  name: PropTypes.string.isRequired,
  size: PropTypes.number.isRequired,
}

Here you are destructuring the props in the parameter list for the AnimalCard function, then displaying the data in a div. The diet data is listed as a single string using the join() method. Each piece of data includes a corresponding PropType to make sure the data type is correct.

在这里,您要对AnimalCard函数的参数列表中的props进行销毁 ,然后在div显示数据。 diet数据使用join()方法作为单个字符串列出。 每条数据都包含一个对应的PropType ,以确保数据类型正确。

Save and close the file.

保存并关闭文件。

Now that you have your component and your data, you need to combine them together. To do that, import the component and the data into the root component of your project: App.js.

现在您已经有了组件和数据,您需要将它们组合在一起。 为此,请将组件和数据导入项目的根组件: App.js

First, open the component:

首先,打开组件:

  • nano src/components/App/App.js

    纳米src / components / App / App.js

From there, you can loop over the data and return a new AnimalCard with the relevant props. Add the highlighted lines to App.js:

从那里,您可以遍历数据并返回带有相关道具的新AnimalCard 。 将突出显示的行添加到App.js

wrapper-tutorial/src/components/App/App.js
wrapper-tutorial / src / components / App / App.js
import React from 'react';
import './App.css';

import animals from './data';
import AnimalCard from '../AnimalCard/AnimalCard';

function App() {
  return (
    <div className="wrapper">
      {animals.map(animal =>
        <AnimalCard
          diet={animal.diet}
          key={animal.name}
          name={animal.name}
          size={animal.size}
        />
      )}
    </div>
  );
}

export default App;

Save and close the file.

保存并关闭文件。

As you work on more complex projects, your data will come from more varied places, such as APIs, localStorage, or static files. But the process for using each of these will be similar: assign the data to a variable and loop over the data. In this case, the data is from a static file, so you are importing directly to a variable.

当您处理更复杂的项目时,您的数据将来自更多不同的地方,例如APIlocalStorage或静态文件。 但是使用它们的过程将是相似的:将数据分配给变量并在数据上循环。 在这种情况下,数据来自静态文件,因此您将直接导入变量。

In this code, you use the .map() method to iterate over animals and display the props. Notice that you do not have to use every piece of data. You are not explicitly passing the scientificName property, for example. You are also adding a separate key prop that React will use to keep track of the mapped data. Finally, you are wrapping the code with a div with a className of wrapper that you’ll use to add some styling.

在此代码中,您使用.map()方法遍历animals并显示道具。 注意,您不必使用所有数据。 例如,您没有显式传递scientificName属性。 您还添加了一个单独的key道具,React将使用它来跟踪映射的数据 。 最后,使用div包装代码,该div具有wrapperclassName ,将用于添加一些样式。

To add this styling, open App.css:

要添加此样式,请打开App.css

  • nano src/components/App/App.css

    纳米src / components / App / App.css

Remove the boilerplate styling and add flex properties to a class called wrapper:

删除样板样式,并将flex属性添加到称为wrapper的类中:

prop-tutorial/src/components/App/App.js
prop-tutorial / src / components / App / App.js
.wrapper {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    padding: 20px;
}

This will use flexbox layout to organize the data so it will line up. padding gives some space in the browser window, and justify-content spreads out the extra space between elements.

这将使用flexbox布局来组织数据,以便使其对齐。 padding会在浏览器窗口中留出一些空间,而justify-content散布元素之间的额外空间。

Save and exit the file. When you do, the browser will refresh and you’ll see some data spaced out.

保存并退出文件。 完成后,浏览器将刷新,并且您会看到一些数据间隔开了。

创建详细信息组件 (Creating a Details Component)

You now have a simple component that displays the data. But let’s say you wanted to give the diet data a little flair by converting the text to an emoji. You can do this by converting the data in your component.

现在,您有一个显示数据的简单组件。 但是,假设您想通过将文本转换为表情符号来使diet数据更具个性。 您可以通过转换组件中的数据来做到这一点。

React is designed to be flexible, so when you are thinking about how to convert data, you have a few different options:

React的设计非常灵活,因此,当您考虑如何转换数据时,有几种不同的选择:

  • You can create a function inside the component that converts the text to an emoji.

    您可以在组件内部创建一个将文本转换为表情符号的函数。
  • You can create a function and store it in a file outside the component so that you can reuse the logic across different components.

    您可以创建一个函数并将其存储在组件外部的文件中,以便可以在不同组件之间重用逻辑。
  • You can create a separate component that converts the text to an emoji.

    您可以创建一个单独的组件,将文本转换为表情符号。

Each approach is fine when applied to the right use case, and you’ll find yourself switching between them as you build an application. To avoid premature abstraction and complexity, you should use the first option to start. If you find yourself wanting to reuse logic, you can pull the function out separately from the component. The third option is best if you want to have a reusable piece that includes the logic and the markup, or that you want to isolate to use across the application.

每种方法都适用于正确的用例,并且在构建应用程序时会发现它们之间会相互切换。 为避免过早的抽象和复杂性,应使用第一个选项启动。 如果您发现自己想重用逻辑,则可以将功能与组件分开。 如果您想拥有一个包含逻辑和标记的可重用部分,或者希望隔离以在整个应用程序中使用,则第三个选项是最佳选择。

In this case, we’ll make a new component, since we will want to add more data later and we are combining markup with conversion logic.

在这种情况下,我们将制作一个新组件,因为我们稍后将要添加更多数据,并且我们将标记与转换逻辑结合在一起。

The new component will be called AnimalDetails. To make it, create a new directory:

新的组件将称为AnimalDetails 。 为此,请创建一个新目录:

  • mkdir src/components/AnimalDetails

    mkdir src / components / AnimalDetails

Next, open AnimalDetails.js in your text editor:

接下来,在文本编辑器中打开AnimalDetails.js

  • nano src/components/AnimalDetails/AnimalDetails.js

    纳米src / components / AnimalDetails / AnimalDetails.js

Inside the file, make a small component that displays the diet as an emoji:

在文件内部,制作一个小的组件,将diet显示为表情符号:

wrapper-tutorial/src/components/AnimalDetails/AnimalDetails.js
wrapper-tutorial / src / components / AnimalDetails / AnimalDetails.js
import React from 'react';
import PropTypes from 'prop-types';
import './AnimalDetails.css';

function convertFood(food) {
  switch(food) {
    case 'insects':
      return '🐜';
    case 'meat':
      return '🍖';
    case 'plants':
    default:
      return '🌱';
  }
}

export default function AnimalDetails({ diet }) {
  return(
    <div className="details">
      <h4>Details:</h4>
      <div>
        Diet: {diet.map(food => convertFood(food)).join(' ')}
      </div>
    </div>
  )
}

AnimalDetails.propTypes = {
  diet: PropTypes.arrayOf(PropTypes.string).isRequired,
}

The AnimalDetails.propTypes object sets up the function to take a prop of diet that is an array of strings. Then inside the component, the code loops over the diet and converts the string to an emoji using the switch statement.

AnimalDetails.propTypes对象设置函数以获取diet道具,该diet道具是字符串数组。 然后在组件内部,代码循环diet并使用switch语句将字符串转换为表情符号。

Save and close the file.

保存并关闭文件。

You are also importing some CSS, so let’s add that now.

您还正在导入一些CSS,所以现在就添加它。

Open AnimalDetails.css:

打开AnimalDetails.css

  • nano src/components/AnimalDetails/AnimalDetails.css

    纳米src / components / AnimalDetails / AnimalDetails.css

Add some CSS to give the element a border and margin to separate the details from the rest of the component:

添加一些CSS,为元素添加边框和边距,以将细节与组件的其余部分分开:

wrapper-tutorial/src/components/AnimalDetails/AnimalDetails.css
wrapper-tutorial / src / components / AnimalDetails / AnimalDetails.css
.details {
    border-top: gray solid 1px;
    margin: 20px 0;
}

We use .details to match the rule to elements with a className of details.

我们使用.details将规则匹配到具有details className的元素。

Save and close the file.

保存并关闭文件。

Now that you have a new custom component, you can add it to your AnimalCard component. Open AnimalCard.js:

现在您有了一个新的自定义组件,可以将其添加到AnimalCard组件中。 打开AnimalCard.js

  • nano src/components/AnimalCard/AnimalCard.js

    纳米src / components / AnimalCard / AnimalCard.js

Replace the diet.join statement with the new AnimalDetails component and pass diet as a prop by adding the highlighted lines:

更换diet.join新声明AnimalDetails组件和传球diet中加入高亮行作为道具:

wrapper-tutorial/src/components/AnimalCard/AnimalCard.js
wrapper-tutorial / src / components / AnimalCard / AnimalCard.js
import React from 'react';
import PropTypes from 'prop-types';
import AnimalDetails from '../AnimalDetails/AnimalDetails';

export default function AnimalCard({ diet, name, size }) {
  return(
    <div>
      <h3>{name}</h3>
      <div>{size}kg</div>
      <AnimalDetails
        diet={diet}
      />
    </div>
  )
}

AnimalCard.propTypes = {
  diet: PropTypes.arrayOf(PropTypes.string).isRequired,
  name: PropTypes.string.isRequired,
  size: PropTypes.number.isRequired,
}

Save the file and you’ll see the new details in the browser.

保存文件,您将在浏览器中看到新的详细信息。

使用...props通过组件传递详细信息 (Passing Details Through a Component with ...props)

The components are working well together, but there’s a slight inefficiency in AnimalCard. You are explicitly pulling diet out from the props argument, but you aren’t using the data. Instead, you are passing it through to the component. There’s nothing inherently wrong about this—in fact, it’s often better to err on the side of too much communication. But in doing this, you make your code more difficult to maintain. Whenever you want to pass new data to AnimalDetails, you need to update three places: App, where you pass the props, AnimalDetails, which consumes the prop, and AnimalCard, which is the go-between.

这些组件可以很好地协同工作,但是AnimalCard效率AnimalCard低下。 您明确地从props参数中退出了diet ,但是您没有使用数据。 而是将其传递给组件。 这本质上没有错,实际上,最好是在过多的交流方面犯错。 但是这样做会使代码更难以维护。 每当您要将新数据传递给AnimalDetails ,都需要更新三个位置: App ,传递道具的位置, AnimalDetails该道具的AnimalDetailsAnimalCard

A better way is to gather any unused props inside AnimalCard and then pass those directly to AnimalDetails. This gives you the chance to make changes to AnimalDetails without changing AnimalCard. In effect, AnimalCard doesn’t need to know anything about the props or the PropTypes that are going into AnimalDetails.

更好的方法是将所有未使用的道具收集在AnimalCard ,然后将其直接传递给AnimalDetails 。 这使您可以在不更改AnimalCard情况下对AnimalDetails进行更改。 实际上, AnimalCard不需要了解有关进入AnimalDetails的props或PropTypes任何信息。

To do that, you’ll use the object rest operator. This operator collects any items that are not pulled out during destructuring and saves them into a new object.

为此,您将使用对象rest运算符 。 该操作员收集销毁期间未拉出的所有项目,并将其保存到新对象中。

Here’s a simple example:

这是一个简单的例子:

const dog = {
    name: 'dog',
    diet: ['meat']
}

const { name, ...props  } = dog;

In this case, the variable name will be 'dog' and the variable props will be { diet: ['meat']}.

在这种情况下,变量name将为'dog' ,变量props将为{ diet: ['meat']}

Up till now, you’ve passed all props as if they were HTML attributes, but you can also use objects to send props. To use an object as a prop, you need to use the spread operator—...props—surrounded with curly braces. This will change each key-value pair into a prop.

到目前为止,您已经传递了所有道具,就好像它们是HTML属性一样,但是您也可以使用对象来发送道具。 要将对象用作道具,您需要使用大括号括起来的散布运算符...props 。 这会将每个键值对变成一个道具。

Open AnimalCard.js:

打开AnimalCard.js

  • nano src/components/AnimalCard/AnimalCard.js

    纳米src / components / AnimalCard / AnimalCard.js

Inside, remove diet from the destructured object and instead collect the rest of the props into a variable called props. Then pass those props directly to AnimalDetails:

在内部,从变形的物体上去除diet ,然后将其余的道具收集到一个名为props的变量中。 然后将这些道具直接传递给AnimalDetails

wrapper-tutorial/src/components/AnimalCard/AnimalCard.js
wrapper-tutorial / src / components / AnimalCard / AnimalCard.js
import React from 'react';
import PropTypes from 'prop-types';
import AnimalDetails from '../AnimalDetails/AnimalDetails';

export default function AnimalCard({ name, size, ...props }) {
  return(
    <div>
      <h3>{name}</h3>
      <div>{size}kg</div>
      <AnimalDetails
        {...props}
      />
    </div>
  )
}

AnimalCard.propTypes = {
  name: PropTypes.string.isRequired,
  size: PropTypes.number.isRequired,
}

Notice that you can remove the diet PropType since you are not using the prop in this component.

请注意,由于未在该组件中使用道具,因此可以删除diet PropType

In this case, you are only passing one prop to AnimalDetails. In cases where you have multiple props, the order will matter. A later prop will overwrite earlier props, so if you have a prop you want to take priority, make sure it is last. This can cause some confusion if your props object has a property that is also a named value.

在这种情况下,您只需将一个道具传递给AnimalDetails 。 如果您有多个道具,顺序将很重要。 较新的道具将覆盖较早的道具,因此,如果您有要优先使用的道具,请确保它是最后一个。 如果您的props对象具有一个也是命名值的属性,则可能会引起混淆。

Save and close the file. The browser will refresh and everything will look the same:

保存并关闭文件。 浏览器将刷新,所有内容看起来都一样:

To see how the ...props object adds flexibility, let’s pass the scientificName to AnimalDetails via the AnimalCard component.

要查看如何...props对象增加了灵活性,让我们通过scientificNameAnimalDetails通过AnimalCard组件。

First, open App.js:

首先,打开App.js

  • nano src/components/App/App.js

    纳米src / components / App / App.js

Then pass the scientificName as a prop:

然后将scientificName作为道具传递:

wrapper-tutorial/src/components/App/App.js
wrapper-tutorial / src / components / App / App.js
import React from 'react';
import './App.css';

import animals from './data';
import AnimalCard from '../AnimalCard/AnimalCard';

function App() {
  return (
    <div className="wrapper">
      {animals.map(animal =>
        <AnimalCard
          diet={animal.diet}
          key={animal.name}
          name={animal.name}
          size={animal.size}
          scientificName={animal.scientificName}
        />
      )}
    </div>
  );
}

export default App;

Save and close the file.

保存并关闭文件。

Skip over AnimalCard; you won’t need to make any changes there. Then open AnimalDetails so you can consume the new prop:

跳过AnimalCard ; 您无需在此处进行任何更改。 然后打开AnimalDetails以便使用新道具:

  • nano src/components/AnimalDetails/AnimalDetails.js

    纳米src / components / AnimalDetails / AnimalDetails.js

The new prop will be a string, which you’ll add to the details list along with a line declaring the PropType:

新的道具将是一个字符串,您将把它添加到details列表以及声明PropType的行:

wrapper-tutorial/src/components/AnimalDetails/AnimalDetails.js
wrapper-tutorial / src / components / AnimalDetails / AnimalDetails.js
import React from 'react';
...
export default function AnimalDetails({ diet, scientificName }) {
  return(
    <div className="details">
      <h4>Details:</h4>
      <div>
        Scientific Name: {scientificName}.
      </div>
      <div>
        Diet: {diet.map(food => convertFood(food)).join(' ')}
      </div>
    </div>
  )
}

AnimalDetails.propTypes = {
  diet: PropTypes.arrayOf(PropTypes.string).isRequired,
  scientificName: PropTypes.string.isRequired,
}

Save and close the file. When you do, the browser will refresh and you’ll see the new details without any changes to the AnimalCard component:

保存并关闭文件。 完成后,浏览器将刷新,您将看到新的详细信息,而无需对AnimalCard组件进行任何更改:

In this step, you learned how to create flexible parent props that can take unknown props and pass them into nested components with the spread operator. This is a common pattern that will give you the flexibility you need to create components with focused responsibilities. In the next step, you’ll create components that can take unknown components as a prop using the built in children prop.

在这一步中,您学习了如何创建灵活的父道具,这些父道具可以接受未知道具,并使用传播运算符将它们传递到嵌套组件中。 这是一种常见的模式,它将为您提供创建具有重点职责的组件所需的灵活性。 在下一步中,您将使用内置的children prop创建可将未知组件作为prop的组件。

第3步-创建带有children组件的包装器组件 (Step 3 — Creating Wrapper Components with children)

In this step, you’ll create a wrapper component that can take an unknown group of components as a prop. This will give you the ability to nest components like standard HTML, and it will give you a pattern for creating reusable wrappers that will let you make a variety of components that need a common design but a flexible interior.

在此步骤中,您将创建一个包装器组件,该组件可以将一组未知的组件用作道具。 这将使您能够嵌套标准HTML之类的组件,并且将为您提供创建可重用包装器的模式,使您可以制作需要通用设计但内部灵活的各种组件。

React gives you a built-in prop called children that collects any children components. Using this makes creating wrapper components intuitivie and readable.

React给你一个内置的道具叫children收集任何儿童的组件。 使用此方法可以直观地创建包装器组件。

To start, make a new component called Card. This will be a wrapper component to create a standard style for any new card components.

首先,创建一个名为Card的新组件。 这将是一个包装器组件,用于为任何新卡组件创建标准样式。

Create a new directory:

创建一个新目录:

  • mkdir src/components/Card

    mkdir src /组件/卡

Then open the Card component in your text editor:

然后在文本编辑器中打开Card组件:

  • nano src/components/Card/Card.js

    纳米src / components / Card / Card.js

Create a component that takes children and title as props and wraps them in a div by adding the following code:

通过添加以下代码,创建一个将childrentitle作为道具的组件并将其包装在div

wrapper-tutorial/src/components/Card/Card.js
wrapper-tutorial / src / components / Card / Card.js
import React from 'react';
import PropTypes from 'prop-types';
import './Card.css';

export default function Card({ children, title }) {
  return(
    <div className="card">
      <div className="card-details">
        <h2>{title}</h2>
      </div>
      {children}
    </div>
  )
}

Card.propTypes = {
  children: PropTypes.oneOfType([
    PropTypes.arrayOf(PropTypes.element), 
    PropTypes.element.isRequired
  ]),
  title: PropTypes.string.isRequired,
}

The PropTypes for the children are new. The children prop can either be a JSX element or an array of JSX elements. The title is a string.

PropTypeschildren都是新的。 children道具可以是JSX元素或JSX元素数组。 title是一个字符串。

Save and close the file.

保存并关闭文件。

Next, add some styling. Open Card.css:

接下来,添加一些样式。 打开Card.css

  • nano src/components/Card/Card.css

    纳米src / components / Card / Card.css

Your card will have a border and a line under the details.

您的卡的详细信息下方将带有边框和一条线。

wrapper-tutorial/src/components/Card/Card.css
wrapper-tutorial / src / components / Card / Card.css
.card {
    border: black solid 1px;
    margin: 10px;
    padding: 10px;
    width: 200px;
}

.card-details {
    border-bottom: gray solid 1px;
    margin-bottom: 20px;
}

Save and close the file. Now that you have your component you need to use it. You could wrap each AnimalCard with the Card component in App.js, but since the name AnimalCard implies it is already a Card, it would be better to use the Card component inside of AnimalCard.

保存并关闭文件。 现在您已经有了组件,您需要使用它。 你可以包装每个AnimalCardCard在组件App.js ,但由于名字AnimalCard意味着它已经是一个Card ,它会更好地利用Card的组件内部AnimalCard

Open up AnimalCard:

打开AnimalCard

  • nano src/components/AnimalCard/AnimalCard.js

    纳米src / components / AnimalCard / AnimalCard.js

Unlike other props, you don’t pass children explicitly. Instead, you include the JSX as if they were HTML child elements. In other words, you just nest them inside of the element, like the following:

与其他道具不同,您不会显式地传递children 。 相反,您将JSX视为HTML子元素。 换句话说,您只需将它们嵌套在元素内部,如下所示:

wrapper-tutorial/src/components/AnimalCard/AnimalCard.js
wrapper-tutorial / src / components / AnimalCard / AnimalCard.js
import React from 'react';
import PropTypes from 'prop-types';
import Card from '../Card/Card';
import AnimalDetails from '../AnimalDetails/AnimalDetails';

export default function AnimalCard({ name, size, ...props }) {
  return(
    <Card title="Animal">
      <h3>{name}</h3>
      <div>{size}kg</div>
      <AnimalDetails
        {...props}
      />
    </Card>
  )
}

AnimalCard.propTypes = {
  name: PropTypes.string.isRequired,
  size: PropTypes.number.isRequired,
}

Unlike a React component, you do not need to have a single root element as a child. That’s why the PropType for Card specified it could be an array of elements or a single element. In addition to passing the children as nested components, you are giving the card a title of Animal.

与React组件不同,您不需要将单个根元素作为子元素。 这就是为什么CardPropType指定它可以是元素数组或单个元素的原因。 除了将children作为嵌套组件传递之外,您还为卡片赋予了Animal标题。

Save and close the file. When you do, the browser will refresh and you’ll see the updated card component.

保存并关闭文件。 完成后,浏览器将刷新,您将看到更新的卡组件。

Now you have a reusable Card component that can take any number of nested children. The primary advantage of this is that you can reuse the Card with any arbitrary component. If you wanted to make a Plant card, you could do that by wrapping the plant information with the Card component. It doesn’t even need to relate at all: If you wanted to reuse the Card component in a completely different applications that lists things like music or account data, you could do that, too. The Card component doesn’t care what the children are; you are just reusing the wrapper element, which in this case is the styled border and title.

现在,您有了可重复使用的Card组件,该组件可以容纳任意数量的嵌套子代。 这样的主要优点是您可以将Card与任意组件一起重复使用。 如果要制作Plant卡,可以通过使用Card组件包装植物信息来实现。 它甚至根本不需要关联:如果您想在列出音乐或帐户数据之类的完全不同的应用程序中重用Card组件,也可以这样做。 Card组件不在乎孩子是什么; 您只是在重用wrapper元素,在这种情况下,它是样式化的边框和标题。

The downside to using children is that you can only have one instance of the child prop. Occasionally, you’ll want a component to have custom JSX in multiple places. Fortunately, you can do that by passing JSX and React components as props, which we will cover in the next step.

使用children的不利之处在于,您只能拥有一个子道具的实例。 有时,您会希望组件在多个位置具有自定义JSX。 幸运的是,您可以通过传递JSX和React组件作为道具来做到这一点,我们将在下一步中介绍。

第4步-将组件作为道具传递 (Step 4 — Passing Components as Props)

In this step, you’ll modify your Card component to take other components as props. This will give your component maximum flexibility to display unknown components or JSX in multiple locations throughout the page. Unlike children, which you can only use once, you can have as many components as props, giving your wrapper component the ability to adapt to a variety of needs while maintaining a standard look and structure.

在此步骤中,您将修改Card组件以将其他组件用作道具。 这将使您的组件具有最大的灵活性,可以在整个页面的多个位置显示未知组件或JSX。 不像children ,你只能使用一次,你可以有很多组件的道具,让您的包装组件,以适应不同的需求,同时保持一个标准的外观和结构的能力。

By the end of this step, you’ll have a component that can wrap children components and also display other components in the card. This pattern will give you flexibility when you need to create components that need information that is more complex than simple strings and integers.

在此步骤结束时,您将拥有一个可以包装子组件并显示卡中其他组件的组件。 当您需要创建需要比简单字符串和整数更复杂的信息的组件时,此模式将为您提供灵活性。

Let’s modify the Card component to take an arbitrary React element called details.

让我们修改Card组件以采用一个名为details的任意React元素。

First, open the Card component:

首先,打开Card组件:

  • nano src/components/Card/Card.js

    纳米src / components / Card / Card.js

Next, add a new prop called details and place it below the <h2> element:

接下来,添加一个名为details的新道具,并将其放在<h2>元素下面:

wrapper-tutorial/src/components/Card/Card.js
wrapper-tutorial / src / components / Card / Card.js
import React from 'react';
import PropTypes from 'prop-types';
import './Card.css';

export default function Card({ children, details, title }) {
  return(
    <div className="card">
      <div className="card-details">
        <h2>{title}</h2>
        {details}
      </div>
      {children}
    </div>
  )
}

Card.propTypes = {
  children: PropTypes.oneOfType([
    PropTypes.arrayOf(PropTypes.element), 
    PropTypes.element.isRequired
  ]),
  details: PropTypes.element,
  title: PropTypes.string.isRequired,
}

Card.defaultProps = {
  details: null,
}

This prop will have the same type as children, but it should be optional. To make it optional, you add a default value of null. In this case, if a user passes no details, the component will still be valid and will not display anything extra.

该道具的类型与children相同,但应为可选。 要使其可选,请添加默认值null 。 在这种情况下,如果用户未传递任何详细信息,则该组件将仍然有效,并且不会显示任何其他内容。

Save and close the file. The page will refresh and you’ll see the same image as before:

保存并关闭文件。 该页面将刷新,您将看到与以前相同的图像:

Now add some details to the AnimalCard. First, open AnimalCard.

现在,向AnimalCard添加一些细节。 首先,打开AnimalCard

  • nano src/components/AnimalCard/AnimalCard.js

    纳米src / components / AnimalCard / AnimalCard.js

Since the Card component is already using children, you’ll need to pass the new JSX component as a prop. Since these are all mammals, add that to the card, but wrap it in <em> tags to make it italic.

由于Card组件已在使用children ,因此您需要将新的JSX组件作为道具传递。 由于这些都是哺乳动物,因此请将其添加到卡片中,然后将其包裹在<em>标签中以使其变为斜体。

wrapper-tutorial/src/components/AnimalCard/AnimalCard.js
wrapper-tutorial / src / components / AnimalCard / AnimalCard.js
import React from 'react';
...

export default function AnimalCard({ name, size, ...props }) {
  return(
    <Card title="Animal" details={<em>Mammal</em>}>
      <h3>{name}</h3>
      <div>{size}kg</div>
      <AnimalDetails
        {...props}
      />
    </Card>
  )
}
...

Save the file. When you do, the browser will refresh and you’ll see the update, including the phrase Mammal.

保存文件。 完成后,浏览器将刷新,您将看到更新,包括短语Mammal

This prop is already powerful because it can take JSX of any size. In this example, you added only a single element, but you could pass as much JSX as you wanted. It also doesn’t have to be JSX. If you have a complicated markup for example, you wouldn’t want to pass it directly in the prop; this would be difficult to read. Instead, you could create a separate component and then pass the component as a prop.

该道具已经强大,因为它可以使用任何大小的JSX。 在此示例中,您仅添加了一个元素,但是可以根据需要传递尽可能多的JSX。 它也不必是JSX。 例如,如果您有一个复杂的标记,则不希望直接在prop中传递它。 这将很难阅读。 相反,您可以创建一个单独的组件,然后将该组件作为道具传递。

To see this at work, pass AnimalDetails to the details prop:

要在工作中看到此效果, AnimalDetails传递给details道具:

wrapper-tutorial/src/components/AnimalCard/AnimalCard.js
wrapper-tutorial / src / components / AnimalCard / AnimalCard.js
import React from 'react';
...

export default function AnimalCard({ name, size, ...props }) {
  return(
    <Card
      title="Animal"
      details={
        <AnimalDetails
          {...props}
        />
      }
    >
      <h3>{name}</h3>
      <div>{size}kg</div>
    </Card>
  )
}
...

AnimalDetails is more complicated and has a number of lines of markup. If you were to add it directly to details, it would increase the prop substantially and make it difficult to read.

AnimalDetails更复杂,并且具有许多行标记。 如果直接将其添加到details ,则会大大增加该道具并使其难以阅读。

Save and close the file. When you do, the browser will refresh and the details will appear at the top of the card.

保存并关闭文件。 完成后,浏览器将刷新,详细信息将显示在卡的顶部。

Now you have a Card component that can take custom JSX and place it in multiple spots. You are not restricted to a single prop; you can pass elements to as many props as you want. This gives you the ability to create flexible wrapping components that can give other developers the opportunity to customize a component while retaining its overall style and functionality.

现在,您有了Card组件,可以使用自定义JSX并将其放置在多个位置。 您不仅仅局限于一个道具。 您可以根据需要将元素传递给尽可能多的道具。 这使您能够创建灵活的包装组件,从而使其他开发人员有机会自定义组件,同时保留其整体样式和功能。

Passing a component as a prop isn’t perfect. It’s a little more difficult to read and isn’t as clear as passing children, but they are just as flexible and you can use as many of them as you want in a component. You should use children first, but don’t hesitate to fall back to props if that is not enough.

将组件作为道具传递并不完美。 读取起来有点困难,不像传递过的children那么清晰,但是它们同样灵活,您可以在组件中随意使用它们。 您应该先使用children ,但如果这还不够的话,请不要犹豫退回道具。

In this step, you learned how to pass JSX and React components as props to another component. This will give your component the flexibility to handle many situations where a wrapper component may need multiple props to handle JSX or components.

在这一步中,您学习了如何将JSX和React组件作为道具传递给另一个组件。 这将使您的组件能够灵活地处理包装器组件可能需要多个道具来处理JSX或组件的许多情况。

结论 (Conclusion)

You have created a variety of wrapping components that can display data flexibly while keeping a predictable look and structure. You created components that can collect and pass unknown props to nested components. You also used the built-in children prop to create wrapper components that can handle an arbitrary number of nested elements. Finally, you created a component that can take JSX or React components as a prop so that your wrapper component can handle multiple instances of different customizations.

您已经创建了各种包装组件,这些组件可以灵活地显示数据,同时保持可预测的外观和结构。 您创建了可以收集未知道具并将其传递给嵌套组件的组件。 您还使用了内置children prop创建了可以处理任意数量的嵌套元素的包装器组件。 最后,您创建了一个可以将JSX或React组件作为道具的组件,以便您的包装器组件可以处理不同自定义项的多个实例。

Wrapper components give you the ability to adapt to unknown circumstances while also maximizing code reuse and consistency. This pattern is useful for creating basic UI elements that you will reuse throughout an application including: buttons, alerts, modals, slide shows, and more. You’ll find yourself returning to it many times.

包装器组件使您能够适应未知情况,同时还可以最大程度地提高代码重用性和一致性。 此模式对于创建可在整个应用程序中重复使用的基本UI元素很有用,包括:按钮,警报,模式,幻灯片放映等等。 您会发现自己回到了很多次。

If you would like to look at more React tutorials, check out our React Topic page, or return to the How To Code in React.js series page.

如果您想查看更多React教程,请查看我们的React Topic页面 ,或者返回到React.js系列中How To Code页面

翻译自: https://www.digitalocean.com/community/tutorials/how-to-create-wrapper-components-in-react-with-props

react中创建一个组件

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值