Skip to content

组件的创建与导出

学习目标

通过本章学习,你将掌握:

  • React组件的各种创建方式
  • ES6模块的导出方式详解
  • 默认导出vs命名导出的选择
  • 组件文件的组织结构
  • React 19的最佳实践
  • 常见问题与解决方案

第一部分:组件创建方式

1.1 函数组件的创建

函数声明

jsx
// 方式1:标准函数声明
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

// 优点:
// - 语法清晰,易于理解
// - 支持函数提升
// - 调试时函数名清晰

// 缺点:
// - 代码稍多

函数表达式

jsx
// 方式2:函数表达式
const Welcome = function(props) {
  return <h1>Hello, {props.name}</h1>;
};

// 优点:
// - 明确的变量赋值
// - 可以使用const保证不被重新赋值

// 缺点:
// - 不支持函数提升
// - 调试时可能显示为匿名函数

箭头函数(推荐)

jsx
// 方式3:箭头函数(最常用)
const Welcome = (props) => {
  return <h1>Hello, {props.name}</h1>;
};

// 简化版(隐式返回)
const Welcome = (props) => <h1>Hello, {props.name}</h1>;

// 解构props
const Welcome = ({ name }) => <h1>Hello, {name}</h1>;

// 优点:
// - 语法简洁
// - 自动绑定this(虽然函数组件不需要this)
// - 支持隐式返回

// 缺点:
// - 调试时显示为匿名函数(需要工具支持)

对比示例

jsx
// 1. 函数声明
function UserCard(props) {
  return (
    <div className="user-card">
      <h3>{props.name}</h3>
      <p>{props.email}</p>
    </div>
  );
}

// 2. 箭头函数 + 解构
const UserCard = ({ name, email }) => (
  <div className="user-card">
    <h3>{name}</h3>
    <p>{email}</p>
  </div>
);

// 3. 箭头函数 + 多行
const UserCard = ({ name, email }) => {
  const handleClick = () => {
    console.log(`Clicked ${name}`);
  };
  
  return (
    <div className="user-card" onClick={handleClick}>
      <h3>{name}</h3>
      <p>{email}</p>
    </div>
  );
};

1.2 类组件的创建

标准类组件

jsx
import React, { Component } from 'react';

// 方式1:继承Component
class Welcome extends Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

// 方式2:继承React.Component
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

类字段语法(推荐)

jsx
class UserCard extends Component {
  // 类字段:状态
  state = {
    isExpanded: false
  };
  
  // 类字段:方法(自动绑定this)
  toggleExpand = () => {
    this.setState({ isExpanded: !this.state.isExpanded });
  };
  
  render() {
    const { name, email } = this.props;
    const { isExpanded } = this.state;
    
    return (
      <div className="user-card">
        <h3>{name}</h3>
        {isExpanded && <p>{email}</p>}
        <button onClick={this.toggleExpand}>
          {isExpanded ? '收起' : '展开'}
        </button>
      </div>
    );
  }
}

构造函数方式

jsx
class UserCard extends Component {
  constructor(props) {
    super(props);
    
    // 初始化状态
    this.state = {
      isExpanded: false
    };
    
    // 绑定方法
    this.toggleExpand = this.toggleExpand.bind(this);
  }
  
  toggleExpand() {
    this.setState({ isExpanded: !this.state.isExpanded });
  }
  
  render() {
    // ...
  }
}

1.3 组件命名规范

组件名必须大写

jsx
// 正确:大写开头(React识别为组件)
function Welcome() {
  return <h1>Hello</h1>;
}

// 错误:小写开头(React识别为HTML标签)
function welcome() {
  return <h1>Hello</h1>;
}

// 使用时的区别
<Welcome />  // 渲染Welcome组件
<welcome />  // 尝试渲染<welcome>这个HTML标签(不存在)

命名约定

jsx
// 1. PascalCase(大驼峰)命名
function UserProfile() { }
function ShoppingCart() { }
function NavBar() { }

// 2. 文件名与组件名一致
// UserProfile.jsx 导出 UserProfile 组件
// ShoppingCart.jsx 导出 ShoppingCart 组件

// 3. 语义化命名
// 好的命名
function LoginButton() { }
function UserAvatar() { }
function ProductList() { }

// 不好的命名
function Btn() { }           // 太简短
function Component1() { }    // 无意义
function MyThing() { }       // 不明确

特殊组件命名

jsx
// 1. 页面组件(Page/View后缀)
function HomePage() { }
function UserProfilePage() { }
function SettingsView() { }

// 2. 容器组件(Container后缀)
function UserListContainer() { }
function AppContainer() { }

// 3. 高阶组件(with前缀)
function withAuth(Component) { }
function withLoading(Component) { }

// 4. Render Props组件
function DataProvider({ render }) {
  return render(data);
}

// 5. Context Provider
const ThemeProvider = ({ children }) => { };

第二部分:模块导出方式

2.1 默认导出(Default Export)

基本语法

jsx
// 方式1:声明后导出
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

export default Welcome;

// 方式2:声明时导出
export default function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

// 方式3:匿名导出
export default function(props) {
  return <h1>Hello, {props.name}</h1>;
}

// 方式4:箭头函数导出
const Welcome = (props) => <h1>Hello, {props.name}</h1>;
export default Welcome;

// 方式5:直接导出
export default (props) => <h1>Hello, {props.name}</h1>;

默认导出的特点

jsx
// 文件:Welcome.jsx
export default function Welcome() {
  return <h1>Hello</h1>;
}

// 导入时可以使用任意名称
import Welcome from './Welcome';      // 原名
import WelcomeComponent from './Welcome';  // 改名
import Hello from './Welcome';        // 完全不同的名字
import W from './Welcome';            // 简写

// 一个文件只能有一个默认导出
export default Welcome;
// export default AnotherComponent;  // 错误!

默认导出的最佳实践

jsx
// 推荐:文件名与组件名一致

// 文件:Button.jsx
export default function Button({ children, onClick }) {
  return <button onClick={onClick}>{children}</button>;
}

// 文件:UserProfile.jsx
export default function UserProfile({ user }) {
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

// 导入
import Button from './Button';
import UserProfile from './UserProfile';

2.2 命名导出(Named Export)

基本语法

jsx
// 方式1:声明后导出
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

function Goodbye(props) {
  return <h1>Goodbye, {props.name}</h1>;
}

export { Welcome, Goodbye };

// 方式2:声明时导出
export function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

export function Goodbye(props) {
  return <h1>Goodbye, {props.name}</h1>;
}

// 方式3:导出时重命名
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

export { Welcome as WelcomeComponent };

命名导出的特点

jsx
// 文件:Greetings.jsx
export function Welcome() {
  return <h1>Welcome</h1>;
}

export function Goodbye() {
  return <h1>Goodbye</h1>;
}

// 导入时必须使用相同的名称
import { Welcome, Goodbye } from './Greetings';

// 导入时重命名
import { Welcome as WelcomeMsg, Goodbye as GoodbyeMsg } from './Greetings';

// 导入全部
import * as Greetings from './Greetings';
// 使用:<Greetings.Welcome />

// 一个文件可以有多个命名导出
export const Button = () => { };
export const Input = () => { };
export const Select = () => { };

命名导出的最佳实践

jsx
// 文件:components.jsx
// 导出多个相关组件

export function Button({ children, onClick }) {
  return <button onClick={onClick}>{children}</button>;
}

export function Input({ value, onChange }) {
  return <input value={value} onChange={onChange} />;
}

export function Select({ options, value, onChange }) {
  return (
    <select value={value} onChange={onChange}>
      {options.map(opt => (
        <option key={opt.value} value={opt.value}>
          {opt.label}
        </option>
      ))}
    </select>
  );
}

// 导入使用
import { Button, Input, Select } from './components';

2.3 混合导出

同时使用默认导出和命名导出

jsx
// 文件:Button.jsx

// 主组件(默认导出)
export default function Button({ children, variant = 'primary', ...props }) {
  return (
    <button className={`btn btn-${variant}`} {...props}>
      {children}
    </button>
  );
}

// 辅助组件(命名导出)
export function IconButton({ icon, children, ...props }) {
  return (
    <Button {...props}>
      <span className="icon">{icon}</span>
      {children}
    </Button>
  );
}

// 辅助函数(命名导出)
export function getButtonClass(variant) {
  return `btn btn-${variant}`;
}

// 常量(命名导出)
export const BUTTON_VARIANTS = {
  PRIMARY: 'primary',
  SECONDARY: 'secondary',
  DANGER: 'danger'
};

导入混合导出

jsx
// 同时导入默认和命名导出
import Button, { IconButton, BUTTON_VARIANTS } from './Button';

function App() {
  return (
    <div>
      <Button variant={BUTTON_VARIANTS.PRIMARY}>主按钮</Button>
      <IconButton icon="🔍">搜索</IconButton>
    </div>
  );
}

2.4 导出方式对比

默认导出 vs 命名导出

jsx
// 场景1:单一组件文件 - 使用默认导出
// UserProfile.jsx
export default function UserProfile({ user }) {
  return <div>{user.name}</div>;
}

// 场景2:多个相关组件 - 使用命名导出
// FormComponents.jsx
export function Input({ ...props }) { }
export function Select({ ...props }) { }
export function Checkbox({ ...props }) { }

// 场景3:主组件 + 辅助内容 - 混合导出
// Card.jsx
export default function Card({ children }) { }
export function CardHeader({ children }) { }
export function CardBody({ children }) { }
export function CardFooter({ children }) { }

选择建议

场景推荐方式原因
单一组件文件默认导出导入简洁,文件名即组件名
工具函数库命名导出明确导入内容,支持tree-shaking
多个相关组件命名导出方便批量导入
主组件+辅助内容混合导出主组件默认,辅助内容命名

第三部分:组件文件组织

3.1 单文件组件结构

jsx
// UserProfile.jsx

// 1. 导入依赖
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { fetchUserData } from '../api';
import './UserProfile.css';

// 2. 常量定义
const DEFAULT_AVATAR = '/images/default-avatar.png';

// 3. 辅助函数
function formatDate(date) {
  return new Date(date).toLocaleDateString('zh-CN');
}

// 4. 子组件(如果很小且仅在此使用)
function Avatar({ src, alt }) {
  return <img src={src || DEFAULT_AVATAR} alt={alt} className="avatar" />;
}

// 5. 主组件
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    fetchUserData(userId).then(data => {
      setUser(data);
      setLoading(false);
    });
  }, [userId]);
  
  if (loading) return <div>Loading...</div>;
  
  return (
    <div className="user-profile">
      <Avatar src={user.avatar} alt={user.name} />
      <h2>{user.name}</h2>
      <p>{user.email}</p>
      <span>注册于: {formatDate(user.createdAt)}</span>
    </div>
  );
}

// 6. PropTypes(类型检查)
UserProfile.propTypes = {
  userId: PropTypes.string.isRequired
};

// 7. 默认导出
export default UserProfile;

3.2 多文件组件结构

方式1:文件夹组件

UserProfile/
├── index.jsx           # 主组件
├── Avatar.jsx          # 子组件
├── UserInfo.jsx        # 子组件
├── UserProfile.css     # 样式
├── constants.js        # 常量
└── helpers.js          # 辅助函数
jsx
// UserProfile/index.jsx
import Avatar from './Avatar';
import UserInfo from './UserInfo';
import { DEFAULT_AVATAR } from './constants';
import { formatDate } from './helpers';
import './UserProfile.css';

export default function UserProfile({ user }) {
  return (
    <div className="user-profile">
      <Avatar src={user.avatar} />
      <UserInfo user={user} />
    </div>
  );
}

// UserProfile/Avatar.jsx
export default function Avatar({ src }) {
  return <img src={src} className="avatar" />;
}

// UserProfile/UserInfo.jsx
export default function UserInfo({ user }) {
  return (
    <div className="user-info">
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

方式2:分层组织

components/
├── UserProfile/
│   ├── index.jsx
│   ├── UserProfile.jsx
│   ├── components/
│   │   ├── Avatar.jsx
│   │   └── UserInfo.jsx
│   ├── styles/
│   │   └── UserProfile.module.css
│   └── utils/
│       ├── constants.js
│       └── helpers.js
jsx
// components/UserProfile/index.jsx
export { default } from './UserProfile';
export * from './components/Avatar';
export * from './components/UserInfo';

// components/UserProfile/UserProfile.jsx
import Avatar from './components/Avatar';
import UserInfo from './components/UserInfo';
import styles from './styles/UserProfile.module.css';

export default function UserProfile({ user }) {
  return (
    <div className={styles.container}>
      <Avatar src={user.avatar} />
      <UserInfo user={user} />
    </div>
  );
}

3.3 项目级别的组织

按功能组织

src/
├── components/          # 通用组件
│   ├── Button/
│   ├── Input/
│   └── Modal/
├── features/           # 功能模块
│   ├── auth/
│   │   ├── LoginForm.jsx
│   │   ├── RegisterForm.jsx
│   │   └── AuthProvider.jsx
│   ├── user/
│   │   ├── UserProfile.jsx
│   │   ├── UserList.jsx
│   │   └── UserCard.jsx
│   └── product/
│       ├── ProductList.jsx
│       └── ProductDetail.jsx
└── pages/              # 页面组件
    ├── HomePage.jsx
    ├── LoginPage.jsx
    └── UserPage.jsx

按类型组织

src/
├── components/         # 所有组件
│   ├── common/        # 通用组件
│   ├── forms/         # 表单组件
│   ├── layout/        # 布局组件
│   └── pages/         # 页面组件
├── hooks/             # 自定义Hooks
├── utils/             # 工具函数
├── services/          # API服务
└── styles/            # 全局样式

React 19推荐结构

src/
├── app/                    # Next.js App Router
│   ├── layout.jsx         # 根布局
│   ├── page.jsx           # 首页
│   └── users/
│       └── [id]/
│           └── page.jsx   # 用户详情页
├── components/            # Client Components
│   ├── ui/               # UI组件
│   └── features/         # 功能组件
├── server-components/    # Server Components
│   ├── UserProfile.jsx
│   └── ProductList.jsx
└── actions/              # Server Actions
    ├── userActions.js
    └── productActions.js

第四部分:React 19最佳实践

4.1 Server Components导出

jsx
// app/components/UserList.jsx (Server Component)

// Server Component默认是async函数
export default async function UserList() {
  // 直接在组件中获取数据
  const users = await db.users.findAll();
  
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

// 导入使用
import UserList from '@/components/UserList';

function Page() {
  return (
    <div>
      <h1>用户列表</h1>
      <UserList />
    </div>
  );
}

4.2 Client Components导出

jsx
// components/Counter.jsx (Client Component)
'use client';  // 必须声明为Client Component

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

// 使用
import Counter from '@/components/Counter';

function Page() {
  return <Counter />;
}

4.3 组件与Server Actions

jsx
// actions/todoActions.js (Server Actions)
'use server';

export async function addTodo(formData) {
  const text = formData.get('text');
  await db.todos.create({ text });
}

export async function deleteTodo(id) {
  await db.todos.delete(id);
}

// 导出多个actions
export { addTodo, deleteTodo };

// components/TodoForm.jsx (Client Component)
'use client';

import { addTodo } from '@/actions/todoActions';

export default function TodoForm() {
  return (
    <form action={addTodo}>
      <input name="text" required />
      <button type="submit">添加</button>
    </form>
  );
}

4.4 use() Hook的组件导出

jsx
// components/UserProfile.jsx
import { use } from 'react';

// 数据获取函数
async function fetchUser(id) {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
}

// 组件使用use()
export default function UserProfile({ userId }) {
  const user = use(fetchUser(userId));
  
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

// 必须在Suspense边界内使用
import { Suspense } from 'react';
import UserProfile from './UserProfile';

function Page() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <UserProfile userId="123" />
    </Suspense>
  );
}

4.5 TypeScript组件导出

tsx
// UserProfile.tsx
import { FC } from 'react';

// 定义Props类型
interface UserProfileProps {
  user: {
    id: string;
    name: string;
    email: string;
  };
  onEdit?: (userId: string) => void;
}

// 方式1:使用FC类型
export const UserProfile: FC<UserProfileProps> = ({ user, onEdit }) => {
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
      {onEdit && <button onClick={() => onEdit(user.id)}>编辑</button>}
    </div>
  );
};

// 方式2:函数声明 + 类型注解
export default function UserProfile({ user, onEdit }: UserProfileProps) {
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
      {onEdit && <button onClick={() => onEdit(user.id)}>编辑</button>}
    </div>
  );
}

// 方式3:类型和组件分开导出
export type { UserProfileProps };
export { UserProfile as default };

第五部分:高级技巧

5.1 懒加载组件导出

jsx
// 动态导入(Code Splitting)
import { lazy, Suspense } from 'react';

// 懒加载组件
const UserProfile = lazy(() => import('./UserProfile'));
const Dashboard = lazy(() => import('./Dashboard'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <UserProfile />
      <Dashboard />
    </Suspense>
  );
}

// 条件懒加载
function App() {
  const [showDashboard, setShowDashboard] = useState(false);
  
  return (
    <div>
      <button onClick={() => setShowDashboard(true)}>
        显示仪表板
      </button>
      
      {showDashboard && (
        <Suspense fallback={<div>Loading...</div>}>
          <Dashboard />
        </Suspense>
      )}
    </div>
  );
}

5.2 条件导出

jsx
// components/index.js

// 根据环境导出不同组件
export { default as Button } from './Button';
export { default as Input } from './Input';

// 开发环境才导出调试组件
if (process.env.NODE_ENV === 'development') {
  export { default as DebugPanel } from './DebugPanel';
}

// 根据功能标志导出
if (process.env.REACT_APP_FEATURE_NEW_UI === 'true') {
  export { default as NewButton } from './NewButton';
} else {
  export { default as Button } from './Button';
}

5.3 批量导出

jsx
// components/index.js

// 方式1:逐个导出
export { default as Button } from './Button';
export { default as Input } from './Input';
export { default as Select } from './Select';
export { default as Checkbox } from './Checkbox';

// 方式2:先导入再导出
import Button from './Button';
import Input from './Input';
import Select from './Select';

export { Button, Input, Select };

// 方式3:重新导出所有
export * from './Button';
export * from './Input';
export * from './Select';

// 使用
import { Button, Input, Select } from './components';

5.4 高阶组件导出

jsx
// hoc/withAuth.jsx

// 高阶组件
export default function withAuth(Component) {
  return function AuthenticatedComponent(props) {
    const { user, loading } = useAuth();
    
    if (loading) return <Spinner />;
    if (!user) return <Redirect to="/login" />;
    
    return <Component {...props} user={user} />;
  };
}

// 使用
import withAuth from './hoc/withAuth';

function Dashboard({ user }) {
  return <div>Welcome, {user.name}</div>;
}

export default withAuth(Dashboard);

// 或者使用装饰器语法(需要babel插件)
@withAuth
class Dashboard extends Component {
  render() {
    return <div>Welcome, {this.props.user.name}</div>;
  }
}

export default Dashboard;

5.5 Render Props导出

jsx
// components/DataProvider.jsx

export default function DataProvider({ url, render }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    fetch(url)
      .then(r => r.json())
      .then(data => {
        setData(data);
        setLoading(false);
      });
  }, [url]);
  
  return render({ data, loading });
}

// 使用
import DataProvider from './DataProvider';

function App() {
  return (
    <DataProvider
      url="/api/users"
      render={({ data, loading }) => (
        loading ? <Spinner /> : <UserList users={data} />
      )}
    />
  );
}

第六部分:常见问题与解决方案

6.1 循环依赖问题

jsx
// 问题:循环依赖
// ComponentA.jsx
import ComponentB from './ComponentB';

export default function ComponentA() {
  return <ComponentB />;
}

// ComponentB.jsx
import ComponentA from './ComponentA';  // 循环依赖!

export default function ComponentB() {
  return <ComponentA />;
}

// 解决方案1:提取共同依赖
// SharedComponent.jsx
export function SharedComponent() {
  return <div>Shared</div>;
}

// ComponentA.jsx
import { SharedComponent } from './SharedComponent';

export default function ComponentA() {
  return <SharedComponent />;
}

// ComponentB.jsx
import { SharedComponent } from './SharedComponent';

export default function ComponentB() {
  return <SharedComponent />;
}

// 解决方案2:使用动态导入
// ComponentA.jsx
import { lazy } from 'react';

const ComponentB = lazy(() => import('./ComponentB'));

export default function ComponentA() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <ComponentB />
    </Suspense>
  );
}

6.2 导入路径问题

jsx
// 问题:相对路径复杂
import Button from '../../../components/Button';
import Input from '../../../components/Input';

// 解决方案1:配置路径别名(webpack/vite)
// vite.config.js
export default {
  resolve: {
    alias: {
      '@': '/src',
      '@components': '/src/components',
      '@utils': '/src/utils'
    }
  }
};

// 使用别名
import Button from '@components/Button';
import Input from '@components/Input';
import { formatDate } from '@utils/date';

// 解决方案2:使用index.js统一导出
// components/index.js
export { default as Button } from './Button';
export { default as Input } from './Input';

// 使用
import { Button, Input } from '@/components';

6.3 命名冲突问题

jsx
// 问题:组件名冲突
import Button from './Button';
import Button from './NewButton';  // 错误:重复声明

// 解决方案1:导入时重命名
import Button from './Button';
import NewButton from './NewButton';

// 解决方案2:使用命名空间
// components/index.js
import * as OldComponents from './old';
import * as NewComponents from './new';

export { OldComponents, NewComponents };

// 使用
import { OldComponents, NewComponents } from './components';

<OldComponents.Button />
<NewComponents.Button />

6.4 默认导出vs命名导出的混淆

jsx
// 问题:混用导致错误

// Button.jsx
export default function Button() { }

// 错误的导入
import { Button } from './Button';  // undefined!

// 正确的导入
import Button from './Button';

// 同样,命名导出不能用默认导入
// Input.jsx
export function Input() { }

// 错误
import Input from './Input';  // undefined!

// 正确
import { Input } from './Input';

6.5 TypeScript导出类型问题

tsx
// 问题:类型和值的混合导出

// UserProfile.tsx
export interface User {
  id: string;
  name: string;
}

export default function UserProfile({ user }: { user: User }) {
  return <div>{user.name}</div>;
}

// 导入时的困惑
import UserProfile from './UserProfile';  // 只导入组件
import { User } from './UserProfile';     // 只导入类型

// 解决方案:明确分离
// types.ts
export interface User {
  id: string;
  name: string;
}

// UserProfile.tsx
import { User } from './types';

export default function UserProfile({ user }: { user: User }) {
  return <div>{user.name}</div>;
}

// 使用
import UserProfile from './UserProfile';
import type { User } from './types';

第七部分:实战案例

7.1 完整的组件库导出

jsx
// components/index.js

// 基础组件
export { default as Button } from './Button';
export { default as Input } from './Input';
export { default as Select } from './Select';

// 复合组件
export { default as Form } from './Form';
export { 
  FormItem,
  FormLabel,
  FormError 
} from './Form';

// 布局组件
export { default as Container } from './Container';
export { 
  Row,
  Col,
  Grid 
} from './Layout';

// 反馈组件
export { default as Modal } from './Modal';
export { default as Toast } from './Toast';
export { 
  showToast,
  hideToast 
} from './Toast';

// 工具函数
export { createTheme } from './theme';
export { configure } from './config';

// 类型定义(TypeScript)
export type {
  ButtonProps,
  InputProps,
  SelectProps
} from './types';

// 使用示例
import {
  Button,
  Input,
  Form,
  FormItem,
  Modal,
  showToast
} from '@mylib/components';

7.2 路由组件导出

jsx
// pages/index.js

// 页面组件
export { default as HomePage } from './HomePage';
export { default as AboutPage } from './AboutPage';
export { default as ContactPage } from './ContactPage';

// 用户相关页面
export { default as LoginPage } from './auth/LoginPage';
export { default as RegisterPage } from './auth/RegisterPage';
export { default as UserProfilePage } from './user/ProfilePage';

// 产品相关页面
export { default as ProductListPage } from './product/ListPage';
export { default as ProductDetailPage } from './product/DetailPage';

// 路由配置使用
import {
  HomePage,
  AboutPage,
  LoginPage,
  ProductListPage
} from './pages';

const routes = [
  { path: '/', component: HomePage },
  { path: '/about', component: AboutPage },
  { path: '/login', component: LoginPage },
  { path: '/products', component: ProductListPage }
];

7.3 React 19 App Router导出

jsx
// app/layout.jsx (根布局)
export default function RootLayout({ children }) {
  return (
    <html lang="zh-CN">
      <body>{children}</body>
    </html>
  );
}

// app/page.jsx (首页)
export default function HomePage() {
  return <h1>首页</h1>;
}

// app/users/[id]/page.jsx (动态路由)
export default function UserPage({ params }) {
  return <h1>用户ID: {params.id}</h1>;
}

// app/api/users/route.js (API路由)
export async function GET(request) {
  const users = await db.users.findAll();
  return Response.json(users);
}

export async function POST(request) {
  const data = await request.json();
  const user = await db.users.create(data);
  return Response.json(user);
}

7.4 自定义Hooks导出

jsx
// hooks/index.js

// 导出所有自定义Hooks
export { default as useAuth } from './useAuth';
export { default as useLocalStorage } from './useLocalStorage';
export { default as useFetch } from './useFetch';
export { default as useDebounce } from './useDebounce';
export { default as useTheme } from './useTheme';

// hooks/useAuth.js
export default function useAuth() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    checkAuth().then(user => {
      setUser(user);
      setLoading(false);
    });
  }, []);
  
  const login = async (credentials) => {
    const user = await api.login(credentials);
    setUser(user);
  };
  
  const logout = () => {
    api.logout();
    setUser(null);
  };
  
  return { user, loading, login, logout };
}

// 使用
import { useAuth, useFetch, useLocalStorage } from '@/hooks';

function App() {
  const { user, login, logout } = useAuth();
  const { data, loading } = useFetch('/api/users');
  const [theme, setTheme] = useLocalStorage('theme', 'light');
  
  // ...
}

第八部分:总结与最佳实践

8.1 导出方式选择指南

jsx
// 1. 单一主要组件 → 默认导出
// Button.jsx
export default function Button() { }

// 2. 多个平等组件 → 命名导出
// FormControls.jsx
export function Input() { }
export function Select() { }
export function Checkbox() { }

// 3. 主组件 + 辅助组件 → 混合导出
// Card.jsx
export default function Card() { }
export function CardHeader() { }
export function CardBody() { }

// 4. 工具函数库 → 命名导出
// utils.js
export function formatDate() { }
export function debounce() { }
export function throttle() { }

// 5. 配置对象 → 默认导出或命名导出
// config.js
export default {
  apiUrl: '...',
  timeout: 5000
};

// 或
export const API_URL = '...';
export const TIMEOUT = 5000;

8.2 文件组织最佳实践

推荐结构:

src/
├── components/           # 组件
│   ├── common/          # 通用组件
│   │   ├── Button/
│   │   │   ├── index.jsx
│   │   │   ├── Button.jsx
│   │   │   ├── Button.test.jsx
│   │   │   └── Button.module.css
│   │   └── Input/
│   ├── features/        # 功能组件
│   └── layout/          # 布局组件
├── hooks/               # 自定义Hooks
├── utils/               # 工具函数
├── services/            # API服务
├── types/               # TypeScript类型
└── app/                 # Next.js App Router

8.3 命名规范总结

jsx
// 组件命名:PascalCase
function UserProfile() { }
class ShoppingCart extends Component { }

// 文件命名:与组件名一致
// UserProfile.jsx
// ShoppingCart.jsx

// 文件夹命名:与主组件一致
// UserProfile/
//   ├── index.jsx
//   └── UserProfile.jsx

// Hooks命名:use前缀 + 驼峰
function useAuth() { }
function useLocalStorage() { }

// 工具函数:驼峰
function formatDate() { }
function debounce() { }

// 常量:全大写 + 下划线
const API_URL = '...';
const MAX_COUNT = 100;

8.4 React 19特定建议

jsx
// 1. Server Components
// 默认导出异步函数
export default async function UserList() {
  const users = await fetchUsers();
  return <ul>{/* ... */}</ul>;
}

// 2. Client Components
// 明确标记'use client'
'use client';
export default function Counter() {
  const [count, setCount] = useState(0);
  return <button>{count}</button>;
}

// 3. Server Actions
// 使用'use server'
'use server';
export async function createUser(formData) {
  // ...
}

// 4. 使用TypeScript
// 导出类型和组件
export type UserProps = { /* ... */ };
export default function User(props: UserProps) { }

练习题

基础练习

  1. 创建一个组件,同时使用默认导出和命名导出
  2. 组织一个包含3个相关组件的文件夹结构
  3. 实现一个工具函数库的批量导出

进阶练习

  1. 创建一个完整的组件库导出结构
  2. 实现条件导出(根据环境变量)
  3. 使用TypeScript编写带类型的组件导出

高级练习

  1. 设计一个大型项目的组件组织结构
  2. 实现一个支持tree-shaking的组件库
  3. 创建一个React 19的Server/Client Components混合项目结构

通过本章学习,你已经掌握了React组件的创建与导出的全部知识。合理的组织结构和导出方式能让你的代码更加清晰、易于维护。继续实践,构建更优秀的React应用!