React组件完全指南

1. 组件基础概念

什么是React组件?

React组件是构建用户界面的基本单元,它是一个可复用的代码片段,接收输入(props)并返回描述界面应该如何显示的React元素。

组件的核心功能

  • 封装性:将相关的逻辑和UI封装在一起
  • 可复用性:可在不同地方重复使用
  • 组合性:可以组合多个组件构建复杂界面
  • 单向数据流:数据从父组件向子组件流动

2. 组件类型

2.1 函数组件(推荐)

jsx 复制代码
// 基础函数组件
function Welcome(props) {
  return <h1>Hello, {props.name}!</h1>;
}

// 箭头函数组件
const Welcome = (props) => {
  return <h1>Hello, {props.name}!</h1>;
}

// 带Hooks的函数组件
import { useState, useEffect } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    document.title = `Count: ${count}`;
  }, [count]);
  
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

2.2 类组件(传统方式)

jsx 复制代码
import React, { Component } from 'react';

class Welcome extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
  
  componentDidMount() {
    document.title = `Count: ${this.state.count}`;
  }
  
  componentDidUpdate() {
    document.title = `Count: ${this.state.count}`;
  }
  
  handleClick = () => {
    this.setState({ count: this.state.count + 1 });
  }
  
  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={this.handleClick}>
          Click me
        </button>
      </div>
    );
  }
}

3. Props(属性)

3.1 Props基础用法

jsx 复制代码
// 父组件传递props
function App() {
  return (
    <div>
      <UserCard 
        name="张三" 
        age={25} 
        email="zhangsan@example.com"
        isActive={true}
      />
    </div>
  );
}

// 子组件接收props
function UserCard({ name, age, email, isActive }) {
  return (
    <div className={`user-card ${isActive ? 'active' : ''}`}>
      <h3>{name}</h3>
      <p>年龄: {age}</p>
      <p>邮箱: {email}</p>
    </div>
  );
}

3.2 Props默认值和类型检查

jsx 复制代码
import PropTypes from 'prop-types';

function Button({ children, variant, size, onClick }) {
  return (
    <button 
      className={`btn btn-${variant} btn-${size}`}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

// 默认props
Button.defaultProps = {
  variant: 'primary',
  size: 'medium'
};

// PropTypes类型检查
Button.propTypes = {
  children: PropTypes.node.isRequired,
  variant: PropTypes.oneOf(['primary', 'secondary', 'danger']),
  size: PropTypes.oneOf(['small', 'medium', 'large']),
  onClick: PropTypes.func
};

3.3 Props传递技巧

jsx 复制代码
// 1. 展开运算符传递props
const userProps = {
  name: "李四",
  age: 30,
  email: "lisi@example.com"
};

<UserCard {...userProps} />

// 2. 透传props
function CardWrapper({ children, ...otherProps }) {
  return (
    <div className="card-wrapper">
      <Card {...otherProps}>
        {children}
      </Card>
    </div>
  );
}

// 3. render props模式
function DataFetcher({ url, render }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setData(data);
        setLoading(false);
      });
  }, [url]);
  
  return render({ data, loading });
}

// 使用render props
<DataFetcher 
  url="/api/users"
  render={({ data, loading }) => (
    loading ? <div>加载中...</div> : <UserList users={data} />
  )}
/>

4. State(状态)

4.1 useState Hook

jsx 复制代码
import { useState } from 'react';

function FormExample() {
  // 基础状态
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  
  // 对象状态
  const [user, setUser] = useState({
    name: '',
    email: '',
    age: 0
  });
  
  // 数组状态
  const [items, setItems] = useState([]);
  
  // 更新对象状态
  const updateUser = (field, value) => {
    setUser(prevUser => ({
      ...prevUser,
      [field]: value
    }));
  };
  
  // 更新数组状态
  const addItem = (item) => {
    setItems(prevItems => [...prevItems, item]);
  };
  
  const removeItem = (index) => {
    setItems(prevItems => prevItems.filter((_, i) => i !== index));
  };
  
  return (
    <form>
      <input 
        value={name}
        onChange={e => setName(e.target.value)}
        placeholder="姓名"
      />
      <input 
        value={email}
        onChange={e => setEmail(e.target.value)}
        placeholder="邮箱"
      />
    </form>
  );
}

4.2 状态更新最佳实践

jsx 复制代码
// ❌ 错误:直接修改状态
const [user, setUser] = useState({ name: 'John', age: 25 });
user.age = 26; // 不要这样做
setUser(user);

// ✅ 正确:创建新对象
setUser(prevUser => ({ ...prevUser, age: 26 }));

// ❌ 错误:依赖当前状态的更新
const [count, setCount] = useState(0);
setCount(count + 1);

// ✅ 正确:使用函数式更新
setCount(prevCount => prevCount + 1);

5. 常用Hooks

5.1 useEffect - 副作用处理

jsx 复制代码
import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  // 组件挂载和userId变化时执行
  useEffect(() => {
    let cancelled = false;
    
    const fetchUser = async () => {
      setLoading(true);
      try {
        const response = await fetch(`/api/users/${userId}`);
        const userData = await response.json();
        
        if (!cancelled) {
          setUser(userData);
          setLoading(false);
        }
      } catch (error) {
        if (!cancelled) {
          setLoading(false);
        }
      }
    };
    
    fetchUser();
    
    // 清理函数,防止内存泄漏
    return () => {
      cancelled = true;
    };
  }, [userId]);
  
  // 只在挂载时执行一次
  useEffect(() => {
    document.title = '用户资料';
    
    return () => {
      document.title = '应用名称'; // 清理
    };
  }, []);
  
  if (loading) return <div>加载中...</div>;
  
  return (
    <div>
      <h1>{user?.name}</h1>
      <p>{user?.email}</p>
    </div>
  );
}

5.2 useContext - 跨组件状态共享

jsx 复制代码
import { createContext, useContext, useState } from 'react';

// 创建Context
const ThemeContext = createContext();

// Provider组件
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  
  const toggleTheme = () => {
    setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
  };
  
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// 使用Context的组件
function ThemedButton() {
  const { theme, toggleTheme } = useContext(ThemeContext);
  
  return (
    <button 
      className={`btn btn-${theme}`}
      onClick={toggleTheme}
    >
      当前主题: {theme}
    </button>
  );
}

5.3 useMemo 和 useCallback - 性能优化

jsx 复制代码
import { useState, useMemo, useCallback } from 'react';

function ExpensiveComponent({ items, filter }) {
  const [count, setCount] = useState(0);
  
  // 缓存昂贵的计算
  const filteredItems = useMemo(() => {
    console.log('过滤计算执行');
    return items.filter(item => 
      item.name.toLowerCase().includes(filter.toLowerCase())
    );
  }, [items, filter]);
  
  // 缓存回调函数
  const handleItemClick = useCallback((item) => {
    console.log('点击了:', item.name);
  }, []);
  
  // 缓存派生状态
  const itemCount = useMemo(() => filteredItems.length, [filteredItems]);
  
  return (
    <div>
      <p>计数: {count}</p>
      <p>过滤后项目数: {itemCount}</p>
      <button onClick={() => setCount(c => c + 1)}>
        增加计数
      </button>
      
      {filteredItems.map(item => (
        <ItemComponent 
          key={item.id}
          item={item}
          onClick={handleItemClick}
        />
      ))}
    </div>
  );
}

6. 组件设计模式

6.1 容器组件和展示组件

jsx 复制代码
// 容器组件 - 处理逻辑和状态
function UserListContainer() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    fetchUsers().then(data => {
      setUsers(data);
      setLoading(false);
    });
  }, []);
  
  const handleDeleteUser = (userId) => {
    setUsers(users.filter(user => user.id !== userId));
  };
  
  return (
    <UserListPresentation 
      users={users}
      loading={loading}
      onDeleteUser={handleDeleteUser}
    />
  );
}

// 展示组件 - 只负责渲染
function UserListPresentation({ users, loading, onDeleteUser }) {
  if (loading) {
    return <div>加载中...</div>;
  }
  
  return (
    <div>
      {users.map(user => (
        <UserCard 
          key={user.id}
          user={user}
          onDelete={() => onDeleteUser(user.id)}
        />
      ))}
    </div>
  );
}

6.2 高阶组件 (HOC)

jsx 复制代码
// 高阶组件 - 添加loading功能
function withLoading(WrappedComponent) {
  return function WithLoadingComponent(props) {
    if (props.loading) {
      return <div>加载中...</div>;
    }
    
    return <WrappedComponent {...props} />;
  };
}

// 使用HOC
const UserListWithLoading = withLoading(UserList);

// 使用
<UserListWithLoading users={users} loading={isLoading} />

6.3 自定义Hook

jsx 复制代码
// 自定义Hook - 数据获取
function useApi(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    let cancelled = false;
    
    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(url);
        const result = await response.json();
        
        if (!cancelled) {
          setData(result);
          setError(null);
        }
      } catch (err) {
        if (!cancelled) {
          setError(err);
        }
      } finally {
        if (!cancelled) {
          setLoading(false);
        }
      }
    };
    
    fetchData();
    
    return () => {
      cancelled = true;
    };
  }, [url]);
  
  return { data, loading, error };
}

// 使用自定义Hook
function UserProfile({ userId }) {
  const { data: user, loading, error } = useApi(`/api/users/${userId}`);
  
  if (loading) return <div>加载中...</div>;
  if (error) return <div>错误: {error.message}</div>;
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

7. 性能优化

7.1 React.memo - 防止不必要的重渲染

jsx 复制代码
import { memo } from 'react';

// 使用memo包装组件
const UserCard = memo(function UserCard({ user, onEdit }) {
  console.log('UserCard渲染');
  
  return (
    <div>
      <h3>{user.name}</h3>
      <p>{user.email}</p>
      <button onClick={() => onEdit(user.id)}>编辑</button>
    </div>
  );
});

// 自定义比较函数
const UserCardWithCustomCompare = memo(UserCard, (prevProps, nextProps) => {
  return prevProps.user.id === nextProps.user.id &&
         prevProps.user.name === nextProps.user.name;
});

7.2 列表优化

jsx 复制代码
// ✅ 正确:使用稳定的key
function TodoList({ todos }) {
  return (
    <ul>
      {todos.map(todo => (
        <TodoItem key={todo.id} todo={todo} />
      ))}
    </ul>
  );
}

// ❌ 错误:使用索引作为key(在动态列表中)
function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo, index) => (
        <TodoItem key={index} todo={todo} />
      ))}
    </ul>
  );
}

7.3 代码分割和懒加载

jsx 复制代码
import { lazy, Suspense } from 'react';

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

function App() {
  return (
    <div>
      <Suspense fallback={<div>加载中...</div>}>
        <HeavyComponent />
      </Suspense>
    </div>
  );
}

8. 最佳实践

8.1 组件命名和结构

jsx 复制代码
// ✅ 好的命名
function UserProfileCard({ user, isEditable, onEdit }) {
  return (
    <div className="user-profile-card">
      <UserAvatar src={user.avatar} alt={user.name} />
      <UserInfo user={user} />
      {isEditable && (
        <EditButton onClick={() => onEdit(user.id)} />
      )}
    </div>
  );
}

// ✅ 清晰的文件结构
components/
  UserProfileCard/
    index.js          // 导出组件
    UserProfileCard.jsx
    UserProfileCard.css
    UserProfileCard.test.js

8.2 事件处理

jsx 复制代码
function TodoItem({ todo, onToggle, onDelete }) {
  // ✅ 使用useCallback优化
  const handleToggle = useCallback(() => {
    onToggle(todo.id);
  }, [todo.id, onToggle]);
  
  const handleDelete = useCallback(() => {
    onDelete(todo.id);
  }, [todo.id, onDelete]);
  
  // ✅ 或者在JSX中直接传递参数
  return (
    <div>
      <input 
        type="checkbox" 
        checked={todo.completed}
        onChange={() => onToggle(todo.id)}
      />
      <span>{todo.text}</span>
      <button onClick={() => onDelete(todo.id)}>删除</button>
    </div>
  );
}

8.3 错误处理

jsx 复制代码
import { ErrorBoundary } from 'react-error-boundary';

// 错误边界组件
function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div role="alert">
      <h2>出错了:</h2>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>重试</button>
    </div>
  );
}

// 使用错误边界
function App() {
  return (
    <ErrorBoundary
      FallbackComponent={ErrorFallback}
      onError={(error, errorInfo) => {
        console.error('错误记录:', error, errorInfo);
      }}
      onReset={() => {
        // 重置状态
      }}
    >
      <MyComponent />
    </ErrorBoundary>
  );
}

9. 常见注意事项

9.1 避免常见错误

jsx 复制代码
// ❌ 错误:在render中定义函数
function BadComponent() {
  return (
    <button onClick={() => {
      // 每次渲染都创建新函数
      console.log('clicked');
    }}>
      点击
    </button>
  );
}

// ✅ 正确:使用useCallback或提取到组件外部
function GoodComponent() {
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);
  
  return <button onClick={handleClick}>点击</button>;
}

// ❌ 错误:在useEffect中缺少依赖
function BadEffect({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, []); // 缺少userId依赖
  
  return <div>{user?.name}</div>;
}

// ✅ 正确:包含所有依赖
function GoodEffect({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]); // 包含userId依赖
  
  return <div>{user?.name}</div>;
}

9.2 内存泄漏防护

jsx 复制代码
function ComponentWithCleanup() {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    let cancelled = false;
    
    // 定时器清理
    const timer = setInterval(() => {
      if (!cancelled) {
        // 更新数据
      }
    }, 1000);
    
    // 事件监听器清理
    const handleResize = () => {
      if (!cancelled) {
        // 处理resize
      }
    };
    window.addEventListener('resize', handleResize);
    
    // 清理函数
    return () => {
      cancelled = true;
      clearInterval(timer);
      window.removeEventListener('resize', handleResize);
    };
  }, []);
  
  return <div>{data}</div>;
}

9.3 条件渲染技巧

jsx 复制代码
function ConditionalRendering({ user, loading, error }) {
  // ✅ 使用早期返回
  if (loading) {
    return <LoadingSpinner />;
  }
  
  if (error) {
    return <ErrorMessage error={error} />;
  }
  
  if (!user) {
    return <EmptyState message="用户不存在" />;
  }
  
  return (
    <div>
      {/* ✅ 使用逻辑与进行条件渲染 */}
      {user.isVip && <VipBadge />}
      
      {/* ✅ 使用三元运算符 */}
      <div className={user.isActive ? 'active' : 'inactive'}>
        {user.name}
      </div>
      
      {/* ❌ 避免使用 && 运算符和falsy值 */}
      {user.posts.length && <PostList posts={user.posts} />}
      
      {/* ✅ 正确的写法 */}
      {user.posts.length > 0 && <PostList posts={user.posts} />}
    </div>
  );
}

10. 调试技巧

10.1 React开发者工具

jsx 复制代码
// 为组件添加displayName便于调试
const UserCard = memo(function UserCard(props) {
  return <div>{/* ... */}</div>;
});
UserCard.displayName = 'UserCard';

// 使用React.StrictMode检测问题
function App() {
  return (
    <React.StrictMode>
      <MyApp />
    </React.StrictMode>
  );
}

10.2 自定义Hook调试

jsx 复制代码
import { useDebugValue } from 'react';

function useCustomHook(value) {
  const [state, setState] = useState(value);
  
  // 在开发者工具中显示调试信息
  useDebugValue(state, state => `当前值: ${state}`);
  
  return [state, setState];
}

React组件是构建现代前端应用的核心,掌握这些概念、模式和最佳实践将帮助你编写更高质量、更可维护的React应用。记住要保持组件简单、可预测,并始终考虑性能和用户体验。

相关推荐
EndingCoder4 小时前
MongoDB基础与Mongoose ODM
服务器·javascript·数据库·mongodb·中间件·node.js
qq7798233404 小时前
React Hooks完全指南
前端·javascript·react.js
Moment4 小时前
性能狂飙!Next.js 16 重磅发布:Turbopack 稳定、编译提速 10 倍!🚀🚀🚀
前端·javascript·后端
软件技术NINI4 小时前
html css js网页制作成品——HTML+CSS仙台有树电视剧网页设计(5页)附源码
javascript·css·html
DoraBigHead5 小时前
React Fiber:从“递归地狱”到“时间切片”的重生之路
前端·javascript·react.js
YUELEI1185 小时前
Vue 安装依赖的集合和小知识
javascript·vue.js·ecmascript
lecepin6 小时前
AI Coding 资讯 2025-10-22
前端·javascript·后端
gustt6 小时前
深入理解 JavaScript 的对象与代理模式(Proxy)
javascript
3秒一个大6 小时前
JavaScript 对象:从字面量到代理模式的灵活世界
javascript