Skip to content

组件的导入与使用

学习目标

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

  • React组件的各种导入方式
  • 组件使用的基本语法和高级技巧
  • Props传递的多种方式
  • 组件组合与嵌套
  • React 19的新特性使用
  • 性能优化与最佳实践

第一部分:组件导入基础

1.1 导入默认导出的组件

基本语法

jsx
// Button.jsx
export default function Button({ children }) {
  return <button>{children}</button>;
}

// App.jsx
import Button from './Button';

function App() {
  return <Button>点击我</Button>;
}

自定义导入名称

jsx
// 可以使用任意名称导入默认导出
import MyButton from './Button';
import Btn from './Button';
import PrimaryButton from './Button';

// 所有这些都是同一个组件
<MyButton>按钮1</MyButton>
<Btn>按钮2</Btn>
<PrimaryButton>按钮3</PrimaryButton>

导入路径规则

jsx
// 1. 相对路径
import Button from './Button';           // 同目录
import Button from '../Button';          // 上级目录
import Button from '../../components/Button';  // 上两级

// 2. 绝对路径(配置别名后)
import Button from '@/components/Button';
import Button from '@components/Button';

// 3. node_modules包
import React from 'react';
import { BrowserRouter } from 'react-router-dom';

// 4. 省略文件扩展名(webpack/vite会自动解析)
import Button from './Button';        // 自动查找 Button.jsx, Button.js, Button.tsx
import Button from './Button.jsx';    // 明确指定扩展名

1.2 导入命名导出的组件

基本语法

jsx
// FormControls.jsx
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>
  );
}

// App.jsx - 导入命名导出
import { Input, Select } from './FormControls';

function App() {
  const [name, setName] = useState('');
  
  return (
    <>
      <Input value={name} onChange={e => setName(e.target.value)} />
      <Select options={options} value={value} onChange={onChange} />
    </>
  );
}

导入时重命名

jsx
// 导入并重命名
import { Input as TextInput, Select as Dropdown } from './FormControls';

function App() {
  return (
    <>
      <TextInput value={name} onChange={handleChange} />
      <Dropdown options={options} value={value} onChange={handleSelect} />
    </>
  );
}

// 避免命名冲突
import { Button } from './OldComponents';
import { Button as NewButton } from './NewComponents';

<Button>旧按钮</Button>
<NewButton>新按钮</NewButton>

批量导入

jsx
// 导入所有命名导出
import * as Forms from './FormControls';

function App() {
  return (
    <>
      <Forms.Input value={name} onChange={handleChange} />
      <Forms.Select options={options} value={value} onChange={handleSelect} />
      <Forms.Checkbox checked={checked} onChange={handleCheck} />
    </>
  );
}

1.3 混合导入

jsx
// Card.jsx
export default function Card({ children }) {
  return <div className="card">{children}</div>;
}

export function CardHeader({ children }) {
  return <div className="card-header">{children}</div>;
}

export function CardBody({ children }) {
  return <div className="card-body">{children}</div>;
}

// App.jsx - 同时导入默认和命名导出
import Card, { CardHeader, CardBody } from './Card';

function App() {
  return (
    <Card>
      <CardHeader>
        <h2>标题</h2>
      </CardHeader>
      <CardBody>
        <p>内容</p>
      </CardBody>
    </Card>
  );
}

1.4 动态导入(懒加载)

jsx
import { lazy, Suspense } from 'react';

// 动态导入组件
const Dashboard = lazy(() => import('./Dashboard'));
const UserProfile = lazy(() => import('./UserProfile'));
const Settings = lazy(() => import('./Settings'));

function App() {
  const [currentPage, setCurrentPage] = useState('dashboard');
  
  return (
    <div>
      <nav>
        <button onClick={() => setCurrentPage('dashboard')}>仪表板</button>
        <button onClick={() => setCurrentPage('profile')}>个人资料</button>
        <button onClick={() => setCurrentPage('settings')}>设置</button>
      </nav>
      
      <Suspense fallback={<div>加载中...</div>}>
        {currentPage === 'dashboard' && <Dashboard />}
        {currentPage === 'profile' && <UserProfile />}
        {currentPage === 'settings' && <Settings />}
      </Suspense>
    </div>
  );
}

条件动态导入

jsx
// 根据条件决定导入哪个组件
function App() {
  const [showNewUI, setShowNewUI] = useState(false);
  
  // 动态选择要导入的组件
  const Component = lazy(() => 
    showNewUI 
      ? import('./NewDashboard')
      : import('./OldDashboard')
  );
  
  return (
    <Suspense fallback={<Loading />}>
      <Component />
    </Suspense>
  );
}

第二部分:组件使用方式

2.1 基本使用

自闭合组件

jsx
// 无子元素的组件使用自闭合标签
<Button />
<Input />
<Image src="..." alt="..." />
<Divider />

// 等价于
<Button></Button>
<Input></Input>

包含子元素的组件

jsx
// 有子元素时必须有闭合标签
<Button>
  点击我
</Button>

<Card>
  <h2>标题</h2>
  <p>内容</p>
</Card>

// 错误示例
<Button>点击我  {/* 缺少闭合标签 */}

2.2 Props传递

基本Props

jsx
// 字符串props(可以不用花括号)
<Button text="点击" color="primary" />
<Button text={'点击'} color={'primary'} />

// 其他类型props必须用花括号
<Counter initialCount={0} />
<UserCard user={userData} />
<TodoList items={todoItems} />

// 布尔值props
<Button disabled={true} />
<Button disabled />  // true的简写

<Input required={false} />
<Input required={!isOptional} />

对象和数组Props

jsx
// 对象props
const user = {
  name: 'Alice',
  age: 25,
  email: 'alice@example.com'
};

<UserCard user={user} />

// 内联对象(注意双花括号)
<UserCard user={{ name: 'Alice', age: 25 }} />

// 数组props
const items = ['Item 1', 'Item 2', 'Item 3'];
<List items={items} />

// 内联数组
<List items={['Item 1', 'Item 2', 'Item 3']} />

函数Props

jsx
// 函数props(事件处理器)
function App() {
  const handleClick = () => {
    console.log('Clicked!');
  };
  
  const handleSubmit = (data) => {
    console.log('Submitted:', data);
  };
  
  return (
    <>
      <Button onClick={handleClick} />
      <Form onSubmit={handleSubmit} />
      
      {/* 内联函数(不推荐,每次渲染创建新函数) */}
      <Button onClick={() => console.log('Clicked!')} />
      
      {/* 传递参数 */}
      <Button onClick={() => handleDelete(item.id)} />
    </>
  );
}

Props展开

jsx
// 使用展开运算符传递所有props
const props = {
  text: '点击',
  color: 'primary',
  size: 'large',
  disabled: false
};

<Button {...props} />

// 等价于
<Button 
  text="点击"
  color="primary"
  size="large"
  disabled={false}
/>

// 覆盖某些props
<Button {...props} disabled={true} />  // disabled会覆盖props中的值

// 先展开再覆盖 vs 先覆盖再展开
<Button disabled={true} {...props} />  // props中的disabled会覆盖true
<Button {...props} disabled={true} />  // true会覆盖props中的disabled

2.3 Children的使用

基本Children

jsx
// Card组件接收children
function Card({ children }) {
  return <div className="card">{children}</div>;
}

// 使用
<Card>
  <h2>标题</h2>
  <p>内容</p>
</Card>

// children可以是任何类型
<Card>Hello World</Card>  // 字符串
<Card>{123}</Card>         // 数字
<Card>
  <Component />            // 组件
</Card>
<Card>
  {items.map(item => <li key={item.id}>{item.text}</li>)}  // 数组
</Card>

多个Children

jsx
// Layout组件接收多个部分
function Layout({ header, sidebar, content, footer }) {
  return (
    <div className="layout">
      <header>{header}</header>
      <aside>{sidebar}</aside>
      <main>{content}</main>
      <footer>{footer}</footer>
    </div>
  );
}

// 使用
<Layout
  header={<Header />}
  sidebar={<Sidebar />}
  content={<MainContent />}
  footer={<Footer />}
/>

Render Props

jsx
// DataProvider使用render prop
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 });
}

// 使用
<DataProvider
  url="/api/users"
  render={({ data, loading }) => (
    loading ? <Spinner /> : <UserList users={data} />
  )}
/>

2.4 组件组合

组合模式

jsx
// 1. 简单组合
function UserDashboard() {
  return (
    <div>
      <Header />
      <Sidebar />
      <MainContent />
      <Footer />
    </div>
  );
}

// 2. 条件组合
function App() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  
  return (
    <div>
      {isLoggedIn ? (
        <>
          <Dashboard />
          <UserProfile />
        </>
      ) : (
        <LoginForm />
      )}
    </div>
  );
}

// 3. 列表组合
function TodoApp() {
  const [todos, setTodos] = useState([]);
  
  return (
    <div>
      <TodoInput onAdd={handleAdd} />
      <TodoList>
        {todos.map(todo => (
          <TodoItem 
            key={todo.id} 
            todo={todo}
            onToggle={handleToggle}
            onDelete={handleDelete}
          />
        ))}
      </TodoList>
    </div>
  );
}

复合组件模式

jsx
// Tabs组件族
function Tabs({ children, defaultIndex = 0 }) {
  const [activeIndex, setActiveIndex] = useState(defaultIndex);
  
  return (
    <div className="tabs">
      {React.Children.map(children, (child, index) => 
        React.cloneElement(child, {
          isActive: index === activeIndex,
          onClick: () => setActiveIndex(index)
        })
      )}
    </div>
  );
}

function Tab({ label, isActive, onClick, children }) {
  return (
    <>
      <button className={isActive ? 'active' : ''} onClick={onClick}>
        {label}
      </button>
      {isActive && <div className="tab-content">{children}</div>}
    </>
  );
}

// 使用
<Tabs defaultIndex={0}>
  <Tab label="标签1">内容1</Tab>
  <Tab label="标签2">内容2</Tab>
  <Tab label="标签3">内容3</Tab>
</Tabs>

第三部分:React 19新特性使用

3.1 use() Hook使用

jsx
import { use, Suspense } from 'react';

// 创建Promise
function fetchUser(id) {
  return fetch(`/api/users/${id}`).then(r => r.json());
}

// 使用use()消费Promise
function UserProfile({ userId }) {
  const user = use(fetchUser(userId));
  
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

// 必须包裹在Suspense中
function App() {
  return (
    <Suspense fallback={<div>加载中...</div>}>
      <UserProfile userId="123" />
    </Suspense>
  );
}

条件使用use()(React 19新特性)

jsx
function ConditionalData({ shouldFetch, userId }) {
  // React 19允许条件使用use()
  const user = shouldFetch ? use(fetchUser(userId)) : null;
  
  return (
    <div>
      {user ? (
        <div>
          <h2>{user.name}</h2>
          <p>{user.email}</p>
        </div>
      ) : (
        <p>未获取数据</p>
      )}
    </div>
  );
}

3.2 Server Components使用

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

// Client Component
'use client';

function UserCard({ user }) {
  const [isExpanded, setIsExpanded] = useState(false);
  
  return (
    <div>
      <h3>{user.name}</h3>
      {isExpanded && <p>{user.email}</p>}
      <button onClick={() => setIsExpanded(!isExpanded)}>
        {isExpanded ? '收起' : '展开'}
      </button>
    </div>
  );
}

// 在页面中使用
function Page() {
  return (
    <div>
      <h1>用户列表</h1>
      <Suspense fallback={<Loading />}>
        <UserList />
      </Suspense>
    </div>
  );
}

3.3 Server Actions使用

jsx
// Server Action
'use server';

export async function createTodo(formData) {
  const text = formData.get('text');
  const todo = await db.todos.create({ text });
  revalidatePath('/todos');
  return todo;
}

// Client Component使用Server Action
'use client';

import { createTodo } from './actions';
import { useActionState } from 'react';

function TodoForm() {
  const [state, formAction, isPending] = useActionState(createTodo, null);
  
  return (
    <form action={formAction}>
      <input name="text" required />
      <button type="submit" disabled={isPending}>
        {isPending ? '添加中...' : '添加'}
      </button>
      {state && <p>已添加: {state.text}</p>}
    </form>
  );
}

3.4 useOptimistic使用

jsx
'use client';

import { useOptimistic } from 'react';

function TodoList({ todos, addTodo }) {
  const [optimisticTodos, addOptimisticTodo] = useOptimistic(
    todos,
    (state, newTodo) => [...state, newTodo]
  );
  
  async function handleSubmit(formData) {
    const newTodo = {
      id: Date.now(),
      text: formData.get('text'),
      completed: false
    };
    
    // 乐观更新UI
    addOptimisticTodo(newTodo);
    
    // 实际提交到服务器
    await addTodo(newTodo);
  }
  
  return (
    <div>
      <ul>
        {optimisticTodos.map(todo => (
          <li key={todo.id}>
            {todo.text}
            {!todos.find(t => t.id === todo.id) && (
              <span className="pending">正在保存...</span>
            )}
          </li>
        ))}
      </ul>
      
      <form action={handleSubmit}>
        <input name="text" />
        <button>添加</button>
      </form>
    </div>
  );
}

3.5 useFormStatus使用

jsx
'use client';

import { useFormStatus } from 'react-dom';

function SubmitButton() {
  const { pending, data, method, action } = useFormStatus();
  
  return (
    <button type="submit" disabled={pending}>
      {pending ? '提交中...' : '提交'}
    </button>
  );
}

function MyForm() {
  async function handleSubmit(formData) {
    'use server';
    await saveData(formData);
  }
  
  return (
    <form action={handleSubmit}>
      <input name="username" />
      <SubmitButton />
    </form>
  );
}

第四部分:高级使用技巧

4.1 高阶组件使用

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

// 使用高阶组件
function Dashboard({ user }) {
  return <div>Welcome, {user.name}</div>;
}

const AuthenticatedDashboard = withAuth(Dashboard);

// 在App中使用
<AuthenticatedDashboard />

组合多个HOC

jsx
// 定义多个HOC
const withAuth = (Component) => (props) => { /* ... */ };
const withLoading = (Component) => (props) => { /* ... */ };
const withErrorBoundary = (Component) => (props) => { /* ... */ };

// 方式1:嵌套使用
const EnhancedComponent = withAuth(withLoading(withErrorBoundary(MyComponent)));

// 方式2:使用compose函数
function compose(...funcs) {
  return funcs.reduce((a, b) => (...args) => a(b(...args)));
}

const enhance = compose(withAuth, withLoading, withErrorBoundary);
const EnhancedComponent = enhance(MyComponent);

// 使用
<EnhancedComponent />

4.2 Render Props使用

jsx
// Render Props组件
function Mouse({ render }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  
  useEffect(() => {
    const handleMouseMove = (e) => {
      setPosition({ x: e.clientX, y: e.clientY });
    };
    
    window.addEventListener('mousemove', handleMouseMove);
    return () => window.removeEventListener('mousemove', handleMouseMove);
  }, []);
  
  return render(position);
}

// 使用方式1:render prop
<Mouse render={({ x, y }) => (
  <div>鼠标位置: {x}, {y}</div>
)} />

// 使用方式2:children as function
function Mouse({ children }) {
  // ... 同上
  return children(position);
}

<Mouse>
  {({ x, y }) => <div>鼠标位置: {x}, {y}</div>}
</Mouse>

4.3 Context使用

jsx
// 创建Context
const ThemeContext = createContext('light');
const UserContext = createContext(null);

// Provider
function App() {
  const [theme, setTheme] = useState('light');
  const [user, setUser] = useState(null);
  
  return (
    <ThemeContext.Provider value={theme}>
      <UserContext.Provider value={user}>
        <Dashboard />
      </UserContext.Provider>
    </ThemeContext.Provider>
  );
}

// 消费Context
function Dashboard() {
  const theme = useContext(ThemeContext);
  const user = useContext(UserContext);
  
  return (
    <div className={theme}>
      Welcome, {user?.name}
    </div>
  );
}

// React 19可以用use()
function Dashboard() {
  const theme = use(ThemeContext);
  const user = use(UserContext);
  
  return (
    <div className={theme}>
      Welcome, {user?.name}
    </div>
  );
}

4.4 Fragment使用

jsx
// 方式1:使用<Fragment>
import { Fragment } from 'react';

function Table() {
  return (
    <table>
      <tbody>
        <Fragment>
          <tr><td>Row 1</td></tr>
          <tr><td>Row 2</td></tr>
        </Fragment>
      </tbody>
    </table>
  );
}

// 方式2:使用<>...</>(短语法)
function Table() {
  return (
    <table>
      <tbody>
        <>
          <tr><td>Row 1</td></tr>
          <tr><td>Row 2</td></tr>
        </>
      </tbody>
    </table>
  );
}

// 带key的Fragment(必须使用完整语法)
function List({ items }) {
  return (
    <>
      {items.map(item => (
        <Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.description}</dd>
        </Fragment>
      ))}
    </>
  );
}

4.5 Portals使用

jsx
import { createPortal } from 'react-dom';

// Modal组件
function Modal({ children, onClose }) {
  return createPortal(
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal-content" onClick={e => e.stopPropagation()}>
        {children}
      </div>
    </div>,
    document.getElementById('modal-root')  // 渲染到body下的modal-root
  );
}

// 使用
function App() {
  const [showModal, setShowModal] = useState(false);
  
  return (
    <div>
      <button onClick={() => setShowModal(true)}>打开模态框</button>
      
      {showModal && (
        <Modal onClose={() => setShowModal(false)}>
          <h2>模态框内容</h2>
          <p>这是通过Portal渲染的</p>
        </Modal>
      )}
    </div>
  );
}

// index.html需要有
// <div id="root"></div>
// <div id="modal-root"></div>

第五部分:性能优化

5.1 避免不必要的重新渲染

jsx
// 问题:父组件更新导致子组件重新渲染
function Parent() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>{count}</button>
      <ExpensiveChild />  {/* 每次Parent更新都会重新渲染 */}
    </div>
  );
}

// 解决方案1:React.memo
const ExpensiveChild = React.memo(function ExpensiveChild() {
  console.log('ExpensiveChild rendered');
  return <div>Expensive component</div>;
});

// 解决方案2:children提升
function Parent({ children }) {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>{count}</button>
      {children}  {/* children不会重新创建 */}
    </div>
  );
}

<Parent>
  <ExpensiveChild />
</Parent>

// 解决方案3:组件拆分
function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

function Parent() {
  return (
    <div>
      <Counter />  {/* 状态隔离 */}
      <ExpensiveChild />
    </div>
  );
}

5.2 优化Props传递

jsx
// 问题:每次渲染创建新的对象/函数
function Parent() {
  return (
    <Child
      style={{ padding: 20 }}  // 每次都是新对象
      onClick={() => console.log('clicked')}  // 每次都是新函数
    />
  );
}

// 解决方案1:提取到组件外部
const style = { padding: 20 };

function Parent() {
  return <Child style={style} onClick={handleClick} />;
}

function handleClick() {
  console.log('clicked');
}

// 解决方案2:useMemo和useCallback
function Parent() {
  const style = useMemo(() => ({ padding: 20 }), []);
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);
  
  return <Child style={style} onClick={handleClick} />;
}

// React 19 Compiler会自动优化
function Parent() {
  // 编译器自动添加memo
  return (
    <Child
      style={{ padding: 20 }}
      onClick={() => console.log('clicked')}
    />
  );
}

5.3 列表渲染优化

jsx
// 正确使用key
function TodoList({ todos }) {
  return (
    <ul>
      {todos.map(todo => (
        <TodoItem
          key={todo.id}  // 使用唯一且稳定的key
          todo={todo}
        />
      ))}
    </ul>
  );
}

// 虚拟滚动(大列表)
import { FixedSizeList } from 'react-window';

function VirtualList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>{items[index].text}</div>
  );
  
  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {Row}
    </FixedSizeList>
  );
}

5.4 懒加载优化

jsx
// 路由级别懒加载
const HomePage = lazy(() => import('./pages/HomePage'));
const AboutPage = lazy(() => import('./pages/AboutPage'));
const ContactPage = lazy(() => import('./pages/ContactPage'));

function App() {
  return (
    <Router>
      <Suspense fallback={<PageLoader />}>
        <Routes>
          <Route path="/" element={<HomePage />} />
          <Route path="/about" element={<AboutPage />} />
          <Route path="/contact" element={<ContactPage />} />
        </Routes>
      </Suspense>
    </Router>
  );
}

// 组件级别懒加载
function Dashboard() {
  const [showChart, setShowChart] = useState(false);
  
  const Chart = lazy(() => import('./Chart'));
  
  return (
    <div>
      <button onClick={() => setShowChart(true)}>显示图表</button>
      
      {showChart && (
        <Suspense fallback={<div>加载图表...</div>}>
          <Chart />
        </Suspense>
      )}
    </div>
  );
}

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

6.1 组件未渲染问题

jsx
// 问题1:组件名小写
function myComponent() {  // 错误!
  return <div>Hello</div>;
}

<myComponent />  // 被当作HTML标签

// 解决:组件名必须大写
function MyComponent() {
  return <div>Hello</div>;
}

<MyComponent />

// 问题2:忘记导出
// Button.jsx
function Button() {
  return <button>Click</button>;
}
// 忘记: export default Button;

// 解决:添加导出
export default function Button() {
  return <button>Click</button>;
}

// 问题3:导入路径错误
import Button from './button';  // 错误:文件名大小写
import Button from './Button';  // 正确

6.2 Props未传递问题

jsx
// 问题:忘记传递必需的props
function UserCard({ user }) {
  return <div>{user.name}</div>;  // user可能是undefined
}

<UserCard />  // 错误:未传递user

// 解决1:传递props
<UserCard user={userData} />

// 解决2:设置默认值
function UserCard({ user = { name: 'Guest' } }) {
  return <div>{user.name}</div>;
}

// 解决3:添加类型检查(PropTypes或TypeScript)
import PropTypes from 'prop-types';

UserCard.propTypes = {
  user: PropTypes.object.isRequired
};

6.3 事件处理器问题

jsx
// 问题:直接调用函数而不是传递引用
<button onClick={handleClick()}>Click</button>  // 错误:立即执行

// 解决1:传递函数引用
<button onClick={handleClick}>Click</button>

// 解决2:使用箭头函数(传递参数时)
<button onClick={() => handleClick(param)}>Click</button>

// 问题:类组件中this丢失
class MyComponent extends Component {
  handleClick() {
    this.setState({ ... });  // this是undefined
  }
  
  render() {
    return <button onClick={this.handleClick}>Click</button>;
  }
}

// 解决1:箭头函数
class MyComponent extends Component {
  handleClick = () => {
    this.setState({ ... });
  };
  
  render() {
    return <button onClick={this.handleClick}>Click</button>;
  }
}

// 解决2:bind
class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick() {
    this.setState({ ... });
  }
  
  render() {
    return <button onClick={this.handleClick}>Click</button>;
  }
}

6.4 异步数据加载问题

jsx
// 问题:组件卸载后仍然setState
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetchUser(userId).then(setUser);  // 组件卸载后仍执行
  }, [userId]);
  
  return <div>{user?.name}</div>;
}

// 解决1:使用cleanup函数
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    let isMounted = true;
    
    fetchUser(userId).then(data => {
      if (isMounted) {
        setUser(data);
      }
    });
    
    return () => {
      isMounted = false;
    };
  }, [userId]);
  
  return <div>{user?.name}</div>;
}

// 解决2:使用AbortController
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    const controller = new AbortController();
    
    fetch(`/api/users/${userId}`, { signal: controller.signal })
      .then(r => r.json())
      .then(setUser)
      .catch(err => {
        if (err.name !== 'AbortError') {
          console.error(err);
        }
      });
    
    return () => controller.abort();
  }, [userId]);
  
  return <div>{user?.name}</div>;
}

// 解决3:React 19的use() Hook
function UserProfile({ userId }) {
  const user = use(fetchUser(userId));
  return <div>{user.name}</div>;
}

第七部分:最佳实践总结

7.1 组件导入建议

jsx
// 1. 导入顺序
// React核心
import React, { useState, useEffect } from 'react';
// 第三方库
import { BrowserRouter, Route } from 'react-router-dom';
import axios from 'axios';
// 本地组件
import Button from './components/Button';
import Input from './components/Input';
// 工具函数
import { formatDate } from './utils';
// 样式
import './App.css';

// 2. 分组导入
import {
  Button,
  Input,
  Select,
  Checkbox
} from './components/forms';

// 3. 使用路径别名
import Button from '@/components/Button';
import { formatDate } from '@/utils/date';

7.2 组件使用建议

jsx
// 1. 保持组件简洁
// 不好
function App() {
  // 100行代码...
  return (
    <div>
      {/* 复杂的JSX */}
    </div>
  );
}

// 好:拆分为多个小组件
function App() {
  return (
    <div>
      <Header />
      <MainContent />
      <Footer />
    </div>
  );
}

// 2. Props传递清晰
// 不好
<UserCard {...user} {...settings} {...handlers} />

// 好
<UserCard
  user={user}
  settings={settings}
  onEdit={handleEdit}
  onDelete={handleDelete}
/>

// 3. 合理使用children
// 不好
<Card header={<h2>Title</h2>} body={<p>Content</p>} />

// 好
<Card>
  <CardHeader>
    <h2>Title</h2>
  </CardHeader>
  <CardBody>
    <p>Content</p>
  </CardBody>
</Card>

7.3 性能优化建议

jsx
// 1. 避免内联对象和函数
// 不好
<Component style={{ margin: 10 }} onClick={() => {}} />

// 好
const style = { margin: 10 };
const handleClick = useCallback(() => {}, []);
<Component style={style} onClick={handleClick} />

// 2. 正确使用key
// 不好
{items.map((item, index) => <Item key={index} {...item} />)}

// 好
{items.map(item => <Item key={item.id} {...item} />)}

// 3. 使用React.memo
const ExpensiveComponent = React.memo(function ExpensiveComponent({ data }) {
  // 复杂渲染逻辑
  return <div>{/* ... */}</div>;
});

// 4. 懒加载大组件
const HeavyComponent = lazy(() => import('./HeavyComponent'));

<Suspense fallback={<Loading />}>
  <HeavyComponent />
</Suspense>

练习题

基础练习

  1. 创建一个App组件,导入并使用3个不同的子组件
  2. 实现组件间的props传递(父到子、子到父)
  3. 使用Fragment和Portal渲染组件

进阶练习

  1. 实现一个使用HOC的权限控制系统
  2. 创建一个使用Render Props的数据获取组件
  3. 使用Context实现主题切换功能

高级练习

  1. 使用React 19的use() Hook实现数据获取
  2. 创建一个Server Component和Client Component混合的应用
  3. 实现一个使用useOptimistic的乐观更新表单

通过本章学习,你已经全面掌握了React组件的导入与使用技巧。合理使用这些技术,能让你的React应用更加高效、易维护。继续实践,成为React高手!