北屋教程网

专注编程知识分享,从入门到精通的编程学习平台

每个开发者都应该知道的10个React最佳实践

1) 保持组件小而专注

一个组件理想情况下应该做好一件事。大型的"上帝组件"难以测试和维护。将大组件拆分为更小、可重用的组件。

不良实践

// 一个做太多事情的组件
function UserDashboard() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [filters, setFilters] = useState({});
  const [pagination, setPagination] = useState({ page: 1, limit: 10 });
  
  // 用户管理逻辑
  const fetchUsers = async () => { /* ... */ };
  const createUser = async () => { /* ... */ };
  const updateUser = async () => { /* ... */ };
  const deleteUser = async () => { /* ... */ };
  
  // 过滤和分页逻辑
  const applyFilters = () => { /* ... */ };
  const handlePageChange = () => { /* ... */ };
  
  // 复杂的渲染逻辑
  return (
    <div>
      {/* 用户列表 */}
      {/* 用户表单 */}
      {/* 过滤器 */}
      {/* 分页 */}
      {/* 统计信息 */}
    </div>
  );
}

清洁实践

// 拆分为多个专注的组件
function UserDashboard() {
  return (
    <div>
      <UserFilters />
      <UserList />
      <UserPagination />
      <UserStats />
    </div>
  );
}

function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(false);
  
  const fetchUsers = async () => { /* ... */ };
  
  return (
    <div>
      {loading ? <LoadingSpinner /> : <UserTable users={users} />}
    </div>
  );
}

2) 使用函数组件和Hooks

对于大多数用例,类组件已经过时。拥抱带有hooks的函数组件来处理状态、副作用等——它们更简单、更简洁。

类组件(过时)

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }
  
  increment = () => {
    this.setState(prevState => ({ count: prevState.count + 1 }));
  };
  
  render() {
    return (
      <button onClick={this.increment}>
        Count: {this.state.count}
      </button>
    );
  }
}

函数组件和Hooks

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

3) 解构Props和State

避免到处使用props.someValue。相反,解构props和state以获得更干净、更可读的代码:

不良实践

function Welcome(props) {
  return <h1>Hello, {props.name}!</h1>;
}

function UserProfile(props) {
  return (
    <div>
      <h2>{props.user.name}</h2>
      <p>{props.user.email}</p>
      <span>{props.user.role}</span>
    </div>
  );
}

清洁实践

function Welcome({ name }) {
  return <h1>Hello, {name}!</h1>;
}

function UserProfile({ user: { name, email, role } }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>{email}</p>
      <span>{role}</span>
    </div>
  );
}

4) 保持JSX可读

长而深度嵌套的JSX难以阅读。用辅助函数或子组件来分解它。

不良实践

function ProductCard({ product }) {
  return (
    <div className="product-card">
      <div className="product-image">
        <img src={product.image} alt={product.name} />
        {product.isNew && <span className="badge">New</span>}
        {product.discount > 0 && (
          <span className="discount-badge">
            -{product.discount}%
          </span>
        )}
      </div>
      <div className="product-info">
        <h3>{product.name}</h3>
        <p>{product.description}</p>
        <div className="price-section">
          <span className="current-price">${product.price}</span>
          {product.originalPrice > product.price && (
            <span className="original-price">
              ${product.originalPrice}
            </span>
          )}
        </div>
        <div className="actions">
          <button className="add-to-cart">Add to Cart</button>
          <button className="wishlist"></button>
        </div>
      </div>
    </div>
  );
}

清洁实践

function ProductCard({ product }) {
  return (
    <div className="product-card">
      <ProductImage product={product} />
      <ProductInfo product={product} />
      <ProductActions product={product} />
    </div>
  );
}

function ProductImage({ product }) {
  return (
    <div className="product-image">
      <img src={product.image} alt={product.name} />
      {product.isNew && <Badge type="new" />}
      {product.discount > 0 && (
        <Badge type="discount" value={product.discount} />
      )}
    </div>
  );
}

function ProductInfo({ product }) {
  return (
    <div className="product-info">
      <h3>{product.name}</h3>
      <p>{product.description}</p>
      <PriceDisplay product={product} />
    </div>
  );
}

function ProductActions({ product }) {
  return (
    <div className="actions">
      <button className="add-to-cart">Add to Cart</button>
      <button className="wishlist"></button>
    </div>
  );
}

5) 使用PropTypes或TypeScript

始终使用PropTypes验证组件props,或者更好的是,迁移到TypeScript以获得更安全、自文档化的代码。

使用PropTypes

import PropTypes from 'prop-types';

function UserCard({ user, onEdit, onDelete }) {
  return (
    <div className="user-card">
      <h3>{user.name}</h3>
      <p>{user.email}</p>
      <div className="actions">
        <button onClick={() => onEdit(user.id)}>Edit</button>
        <button onClick={() => onDelete(user.id)}>Delete</button>
      </div>
    </div>
  );
}

UserCard.propTypes = {
  user: PropTypes.shape({
    id: PropTypes.number.isRequired,
    name: PropTypes.string.isRequired,
    email: PropTypes.string.isRequired,
  }).isRequired,
  onEdit: PropTypes.func.isRequired,
  onDelete: PropTypes.func.isRequired,
};

使用TypeScript

interface User {
  id: number;
  name: string;
  email: string;
}

interface UserCardProps {
  user: User;
  onEdit: (id: number) => void;
  onDelete: (id: number) => void;
}

function UserCard({ user, onEdit, onDelete }: UserCardProps) {
  return (
    <div className="user-card">
      <h3>{user.name}</h3>
      <p>{user.email}</p>
      <div className="actions">
        <button onClick={() => onEdit(user.id)}>Edit</button>
        <button onClick={() => onDelete(user.id)}>Delete</button>
      </div>
    </div>
  );
}

6) 利用React DevTools

在浏览器中使用React Developer Tools来检查组件树、props和state——它将为您节省数小时调试棘手问题的时间。

使用DevTools的技巧

  1. 组件树检查:查看组件层次结构
  2. Props和State检查:实时查看数据流
  3. 性能分析:识别不必要的重新渲染
  4. Profiler:分析组件渲染时间

7) 记忆化昂贵操作

使用React.memo、useMemo和useCallback避免不必要的重新渲染,特别是对于大型列表或密集计算。

使用React.memo

const ExpensiveComponent = React.memo(function ExpensiveComponent({ data }) {
  // 昂贵的计算
  const processedData = data.map(item => ({
    ...item,
    processed: heavyComputation(item)
  }));
  
  return (
    <div>
      {processedData.map(item => (
        <div key={item.id}>{item.processed}</div>
      ))}
    </div>
  );
});

使用useMemo

function ProductList({ products, filters }) {
  const filteredProducts = useMemo(() => {
    return products.filter(product => {
      return filters.category === 'all' || 
             product.category === filters.category;
    });
  }, [products, filters.category]);
  
  const sortedProducts = useMemo(() => {
    return [...filteredProducts].sort((a, b) => {
      if (filters.sortBy === 'price') {
        return a.price - b.price;
      }
      return a.name.localeCompare(b.name);
    });
  }, [filteredProducts, filters.sortBy]);
  
  return (
    <div>
      {sortedProducts.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

使用useCallback

function ParentComponent() {
  const [count, setCount] = useState(0);
  
  const handleClick = useCallback(() => {
    console.log('Button clicked');
  }, []); // 空依赖数组,函数永远不会改变
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
      <ChildComponent onAction={handleClick} />
    </div>
  );
}

const ChildComponent = React.memo(function ChildComponent({ onAction }) {
  return <button onClick={onAction}>Child Action</button>;
});

8) 清理副作用

使用useEffect时,始终清理订阅、定时器或事件监听器以防止内存泄漏。

正确的useEffect使用

function Timer() {
  const [time, setTime] = useState(new Date());
  
  useEffect(() => {
    const id = setInterval(() => {
      setTime(new Date());
    }, 1000);
    
    return () => clearInterval(id); // 清理!
  }, []);
  
  return <div>Current time: {time.toLocaleTimeString()}</div>;
}

事件监听器清理

function WindowResizeHandler() {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });
  
  useEffect(() => {
    const handleResize = () => {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    };
    
    window.addEventListener('resize', handleResize);
    
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);
  
  return (
    <div>
      Window size: {windowSize.width} x {windowSize.height}
    </div>
  );
}

API订阅清理

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    let isMounted = true;
    
    const fetchUser = async () => {
      try {
        const response = await fetch(`/api/users/${userId}`);
        const userData = await response.json();
        
        if (isMounted) {
          setUser(userData);
          setLoading(false);
        }
      } catch (error) {
        if (isMounted) {
          console.error('Failed to fetch user:', error);
          setLoading(false);
        }
      }
    };
    
    fetchUser();
    
    return () => {
      isMounted = false; // 防止在组件卸载后设置状态
    };
  }, [userId]);
  
  if (loading) return <div>Loading...</div>;
  if (!user) return <div>User not found</div>;
  
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

9) 尽可能保持状态本地

不要不必要地提升状态。本地状态减少复杂性和重新渲染,使您的组件更快、更容易维护。

不必要的状态提升

// 父组件管理所有状态
function ParentComponent() {
  const [child1Data, setChild1Data] = useState('');
  const [child2Data, setChild2Data] = useState('');
  const [child3Data, setChild3Data] = useState('');
  
  return (
    <div>
      <Child1 data={child1Data} setData={setChild1Data} />
      <Child2 data={child2Data} setData={setChild2Data} />
      <Child3 data={child3Data} setData={setChild3Data} />
    </div>
  );
}

本地状态管理

// 每个组件管理自己的状态
function ParentComponent() {
  return (
    <div>
      <Child1 />
      <Child2 />
      <Child3 />
    </div>
  );
}

function Child1() {
  const [data, setData] = useState('');
  // 只在需要时使用本地状态
  return <input value={data} onChange={e => setData(e.target.value)} />;
}

何时提升状态

// 当多个组件需要共享状态时
function TodoApp() {
  const [todos, setTodos] = useState([]);
  
  const addTodo = (text) => {
    setTodos([...todos, { id: Date.now(), text, completed: false }]);
  };
  
  const toggleTodo = (id) => {
    setTodos(todos.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  };
  
  return (
    <div>
      <TodoForm onAdd={addTodo} />
      <TodoList todos={todos} onToggle={toggleTodo} />
      <TodoStats todos={todos} />
    </div>
  );
}

10) 使用有意义的命名

始终为组件、props和hooks使用清晰、描述性的名称。像Button、handleClick或isLoading这样的名称使您的代码自解释,更容易被他人理解。

不良命名

function Comp({ d, f }) {
  const [l, setL] = useState(false);
  
  const hc = () => {
    setL(true);
    f(d);
  };
  
  return <button onClick={hc}>Click</button>;
}

有意义的命名

function UserButton({ user, onUserClick }) {
  const [isLoading, setIsLoading] = useState(false);
  
  const handleClick = () => {
    setIsLoading(true);
    onUserClick(user);
  };
  
  return (
    <button 
      onClick={handleClick}
      disabled={isLoading}
    >
      {isLoading ? 'Loading...' : 'View Profile'}
    </button>
  );
}

命名约定

// 组件:PascalCase
function UserProfile() { }
function ProductCard() { }
function NavigationMenu() { }

// Props:camelCase
function Button({ onClick, isDisabled, children }) { }

// 事件处理函数:handle + 事件名
function Form() {
  const handleSubmit = () => { };
  const handleInputChange = () => { };
  const handleButtonClick = () => { };
}

// 布尔props:is/has/can前缀
function Modal({ isOpen, hasOverlay, canClose }) { }

// 状态:描述性名称
const [isLoading, setIsLoading] = useState(false);
const [userData, setUserData] = useState(null);
const [errorMessage, setErrorMessage] = useState('');

实际应用示例

1. 完整的用户管理组件

// 主组件
function UserManagement() {
  return (
    <div className="user-management">
      <UserHeader />
      <UserFilters />
      <UserList />
      <UserPagination />
    </div>
  );
}

// 用户列表组件
function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  
  const fetchUsers = useCallback(async () => {
    setLoading(true);
    try {
      const response = await fetch('/api/users');
      const data = await response.json();
      setUsers(data);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }, []);
  
  useEffect(() => {
    fetchUsers();
  }, [fetchUsers]);
  
  if (loading) return <LoadingSpinner />;
  if (error) return <ErrorMessage message={error} />;
  
  return (
    <div className="user-list">
      {users.map(user => (
        <UserCard key={user.id} user={user} />
      ))}
    </div>
  );
}

// 用户卡片组件
const UserCard = React.memo(function UserCard({ user }) {
  const [isEditing, setIsEditing] = useState(false);
  
  const handleEdit = useCallback(() => {
    setIsEditing(true);
  }, []);
  
  const handleSave = useCallback(async (updatedUser) => {
    try {
      await updateUser(updatedUser);
      setIsEditing(false);
    } catch (error) {
      console.error('Failed to update user:', error);
    }
  }, []);
  
  return (
    <div className="user-card">
      {isEditing ? (
        <UserEditForm 
          user={user} 
          onSave={handleSave}
          onCancel={() => setIsEditing(false)}
        />
      ) : (
        <UserDisplay 
          user={user} 
          onEdit={handleEdit}
        />
      )}
    </div>
  );
});

2. 自定义Hook示例

// 自定义Hook:数据获取
function useDataFetching(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    let isMounted = true;
    
    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(url);
        const result = await response.json();
        
        if (isMounted) {
          setData(result);
          setError(null);
        }
      } catch (err) {
        if (isMounted) {
          setError(err.message);
        }
      } finally {
        if (isMounted) {
          setLoading(false);
        }
      }
    };
    
    fetchData();
    
    return () => {
      isMounted = false;
    };
  }, [url]);
  
  return { data, loading, error };
}

// 使用自定义Hook
function ProductList() {
  const { data: products, loading, error } = useDataFetching('/api/products');
  
  if (loading) return <LoadingSpinner />;
  if (error) return <ErrorMessage message={error} />;
  
  return (
    <div className="product-list">
      {products.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

总结

记住:小组件 + hooks + 有意义的命名 = 快乐的开发者和可维护的应用!

这些最佳实践将帮助您:

  • 编写更干净、更可维护的代码
  • 提高应用性能
  • 减少bug和调试时间
  • 改善团队协作

您最喜欢哪个最佳实践?在下面评论!


控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言