Appearance
组件的创建与导出
学习目标
通过本章学习,你将掌握:
- 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.jsjsx
// 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 Router8.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) { }练习题
基础练习
- 创建一个组件,同时使用默认导出和命名导出
- 组织一个包含3个相关组件的文件夹结构
- 实现一个工具函数库的批量导出
进阶练习
- 创建一个完整的组件库导出结构
- 实现条件导出(根据环境变量)
- 使用TypeScript编写带类型的组件导出
高级练习
- 设计一个大型项目的组件组织结构
- 实现一个支持tree-shaking的组件库
- 创建一个React 19的Server/Client Components混合项目结构
通过本章学习,你已经掌握了React组件的创建与导出的全部知识。合理的组织结构和导出方式能让你的代码更加清晰、易于维护。继续实践,构建更优秀的React应用!