导入和导出组件

组件的魔力在于其可复用性:你可以创建由其他组件组成的组件。但随着你嵌套越来越多的组件,通常把它们拆分到不同文件中会更合理。这可以让你的文件更易于浏览,并在更多地方复用组件。

You will learn

  • 什么是根组件文件
  • 如何导入和导出组件
  • 何时使用默认导入/导出和命名导入/导出
  • 如何从一个文件中导入和导出多个组件
  • 如何将组件拆分到多个文件中

根组件文件

你的第一个组件 中,你创建了一个 Profile 组件和一个渲染它的 Gallery 组件:

function Profile() {
  return (
    <img
      src="https://react.dev/images/docs/scientists/MK3eW3As.jpg"
      alt="Katherine Johnson"
    />
  );
}

export default function Gallery() {
  return (
    <section>
      <h1>令人惊叹的科学家</h1>
      <Profile />
      <Profile />
      <Profile />
    </section>
  );
}

这些内容目前位于一个根组件文件中,在这个示例里它的名字是 App.js。不过,具体取决于你的设置,根组件也可能位于其他文件中。如果你使用的是基于文件路由的框架,例如 Next.js,那么每个页面的根组件都不同。

导出和导入组件

如果你将来想把着陆页改成展示一份科学书籍列表呢?或者把所有个人资料放到别的地方呢?把 GalleryProfile 移出根组件文件是很合理的。这样可以让它们更模块化,也更容易在其他文件中复用。你可以分三步移动一个组件:

  1. 创建 一个新的 JS 文件来放这些组件。
  2. 从那个文件中导出你的函数组件(使用 默认 导出或 命名 导出)。
  3. 在你要使用该组件的文件中导入它(使用对应的 默认 导入或 命名 导入方式)。

这里,ProfileGallery 都已经从 App.js 移动到了一个名为 Gallery.js 的新文件中。现在你可以修改 App.js,从 Gallery.js 导入 Gallery

import Gallery from './Gallery.js';

export default function App() {
  return (
    <Gallery />
  );
}

注意这个示例现在被拆分成了两个组件文件:

  1. Gallery.js
    • 定义了 Profile 组件,它仅在同一个文件中使用,并且没有被导出。
    • Gallery 组件作为默认导出导出。
  2. App.js
    • Gallery.js 中将 Gallery 作为默认导入导入。
    • 将根 App 组件作为默认导出导出。

Note

你可能会遇到省略 .js 文件扩展名的写法,例如:

import Gallery from './Gallery';

'./Gallery.js''./Gallery' 都可以在 React 中正常工作,不过前者更接近 原生 ES Modules 的工作方式。

Deep Dive

默认导出与命名导出

用 JavaScript 导出值主要有两种方式:默认导出和命名导出。到目前为止,我们的示例只使用了默认导出。但你可以在同一个文件中使用其中一种或两种。一个文件最多只能有一个 默认 导出,但可以拥有任意多个 命名 导出。

默认导出和命名导出

你如何导出组件,决定了你必须如何导入它。如果你尝试以命名导出的方式导入默认导出,就会报错!这个表格可以帮助你区分:

语法导出语句导入语句
默认export default function Button() {}import Button from './Button.js';
命名export function Button() {}import { Button } from './Button.js';

当你写一个 默认 导入时,import 后面可以使用任何你想要的名字。例如,你也可以写成 import Banana from './Button.js',它仍然会给你同样的默认导出。相反,对于命名导入,两边的名字必须匹配。这就是它们被称为 命名 导入的原因!

如果文件只导出一个组件,人们通常会使用默认导出;如果文件导出多个组件和其他值,则通常使用命名导出。 无论你偏好哪种编码风格,都要始终给组件函数以及包含它们的文件起有意义的名字。像 export default () => {} 这样的无名组件不被推荐,因为它们会让调试更困难。

从同一个文件中导出和导入多个组件

如果你只想显示一个 Profile,而不是一个画廊呢?你也可以导出 Profile 组件。不过 Gallery.js 已经有一个 默认 导出了,你不能有 两个 默认导出。你可以创建一个带默认导出的新文件,或者给 Profile 添加一个 命名 导出。一个文件只能有一个默认导出,但可以有很多命名导出!

Note

为了减少默认导出和命名导出之间可能产生的混淆,有些团队选择只坚持一种风格(默认或命名),或者避免在同一个文件中混用它们。选择最适合你的方式即可!

首先,使用命名导出从 Gallery.js 导出 Profile(不使用 default 关键字):

export function Profile() {
// ...
}

然后,使用命名导入(带花括号)将 ProfileGallery.js 导入App.js

import { Profile } from './Gallery.js';

最后,从 App 组件中渲染 <Profile />

export default function App() {
return <Profile />;
}

现在 Gallery.js 包含两个导出:一个默认的 Gallery 导出,以及一个命名的 Profile 导出。App.js 同时导入了它们。尝试在这个示例中把 <Profile /> 改成 <Gallery />,再改回来:

import Gallery from './Gallery.js';
import { Profile } from './Gallery.js';

export default function App() {
  return (
    <Profile />
  );
}

现在你正在混合使用默认导出和命名导出:

  • Gallery.js
    • Profile 组件作为一个名为 Profile 的命名导出导出。
    • Gallery 组件作为默认导出导出。
  • App.js
    • Gallery.js 中将 Profile 作为一个名为 Profile 的命名导入导入。
    • Gallery.js 中将 Gallery 作为默认导入导入。
    • 将根 App 组件作为默认导出导出。

Recap

在本页中你学到了:

  • 什么是根组件文件
  • 如何导入和导出组件
  • 何时以及如何使用默认导入/导出和命名导入/导出
  • 如何从同一个文件中导出多个组件

Challenge 1 of 1:
进一步拆分组件

目前,Gallery.js 同时导出 ProfileGallery,这有点让人困惑。

Profile 组件移动到它自己的 Profile.js 中,然后把 App 组件改为依次渲染 <Profile /><Gallery />

你可以为 Profile 使用默认导出或命名导出,但请确保在 App.jsGallery.js 中使用对应的导入语法!你可以参考上面深度解析中的表格:

语法导出语句导入语句
默认export default function Button() {}import Button from './Button.js';
命名export function Button() {}import { Button } from './Button.js';
// 把我移动到 Profile.js!
export function Profile() {
  return (
    <img
      src="https://react.dev/images/docs/scientists/QIrZWGIs.jpg"
      alt="Alan L. Hart"
    />
  );
}

export default function Gallery() {
  return (
    <section>
      <h1>令人惊叹的科学家</h1>
      <Profile />
      <Profile />
      <Profile />
    </section>
  );
}

在先用一种导出方式把它做通之后,再用另一种方式实现一次。