React的高阶组件:组件复用的新高度

81 阅读4分钟

你是否曾经在React项目中写过这样的代码:

const UserList = ({ users }) => {
  if (users.length === 0) {
    return <p>加载中...</p>;
  }
  return (
    <ul>
      {users.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  );
};

const ProductList = ({ products }) => {
  if (products.length === 0) {
    return <p>加载中...</p>;
  }
  return (
    <ul>
      {products.map(product => <li key={product.id}>{product.name}</li>)}
    </ul>
  );
};

看起来很眼熟对吧?这两个组件除了处理的数据类型不同,其他逻辑几乎一模一样。如果你的项目中充斥着这样的重复代码,那么恭喜你,你已经走上了"复制粘贴工程师"的不归路。

但是别慌!今天我们就来学习一个能让你脱离苦海的神器 —— 高阶组件(Higher-Order Components,简称HOC)。

什么是高阶组件?

高阶组件说白了就是一个函数,这个函数接收一个组件作为参数,然后返回一个新的组件。听起来有点像高阶函数是不是?没错,它们的思想是一脉相承的。

const EnhancedComponent = higherOrderComponent(WrappedComponent);

高阶组件并不是React API的一部分,它只是一种基于React组合特性的模式。这种模式让我们可以抽象出组件之间的共同逻辑,实现更好的代码复用。

为什么需要高阶组件?

  1. 代码复用:HOC可以将通用的逻辑抽象出来,避免在多个组件中重复编写相同的代码。
  2. 条件渲染:可以根据特定条件决定是否渲染组件。
  3. props操作:可以修改、添加或者删除传递给被包装组件的props。
  4. 状态抽象:可以将状态逻辑从组件中抽离,实现状态的集中管理。

如何创建一个高阶组件?

让我们通过一个实际的例子来看看如何创建和使用高阶组件。还记得开头那两个重复的列表组件吗?我们来用HOC重构它们:

// 这就是我们的高阶组件
function withLoading(WrappedComponent) {
  return function({ isLoading, data, ...otherProps }) {
    if (isLoading) {
      return <p>加载中...</p>;
    }
    return <WrappedComponent data={data} {...otherProps} />;
  }
}

// 使用HOC包装我们的列表组件
const UserList = ({ data }) => (
  <ul>
    {data.map(user => <li key={user.id}>{user.name}</li>)}
  </ul>
);

const ProductList = ({ data }) => (
  <ul>
    {data.map(product => <li key={product.id}>{product.name}</li>)}
  </ul>
);

const UserListWithLoading = withLoading(UserList);
const ProductListWithLoading = withLoading(ProductList);

// 在父组件中使用
function App() {
  const [usersLoading, setUsersLoading] = useState(true);
  const [productsLoading, setProductsLoading] = useState(true);
  const [users, setUsers] = useState([]);
  const [products, setProducts] = useState([]);

  // 模拟数据加载
  useEffect(() => {
    setTimeout(() => {
      setUsers([{id: 1, name: '张三'}, {id: 2, name: '李四'}]);
      setUsersLoading(false);
    }, 1000);
    setTimeout(() => {
      setProducts([{id: 1, name: '手机'}, {id: 2, name: '电脑'}]);
      setProductsLoading(false);
    }, 1500);
  }, []);

  return (
    <div>
      <UserListWithLoading isLoading={usersLoading} data={users} />
      <ProductListWithLoading isLoading={productsLoading} data={products} />
    </div>
  );
}

看到没?通过使用withLoading这个高阶组件,我们成功地将加载状态的处理逻辑抽象了出来。无论是用户列表还是产品列表,甚至是将来可能出现的其他类型的列表,都可以复用这个逻辑。这就是HOC的魅力所在!

HOC的注意事项

  1. 不要在render方法中使用HOC

    render() {
      // 错误示范!每次render调用都会创建一个新的EnhancedComponent
      const EnhancedComponent = withLoading(MyComponent);
      return <EnhancedComponent />;
    }
    

    这样做会导致每次渲染都创建一个新的组件,造成不必要的重复渲染和状态丢失。

  2. 务必复制静态方法

    当你使用HOC包装一个组件时,原始组件的静态方法会丢失。确保在HOC中手动复制这些方法:

    function withLoading(WrappedComponent) {
      class WithLoading extends React.Component {/* ... */}
      // 复制静态方法
      return hoistNonReactStatic(WithLoading, WrappedComponent);
    }
    
  3. 谨慎使用refs

    HOC无法将refs传递给被包装的组件。如果你需要访问被包装组件的ref,可以使用React.forwardRef API。

总结

高阶组件是React中实现代码复用的强大工具。它让我们能够以一种优雅的方式抽象和共享组件逻辑。然而,就像所有的编程模式一样,HOC并不是银弹。在某些场景下,React Hooks可能是更好的选择。

记住,编程的艺术在于权衡。选择合适的工具来解决问题,而不是为了使用工具而使用工具。所以,下次当你发现自己在复制粘贴组件代码时,问问自己:「这是不是一个使用HOC的好机会?」

好了,话说到这里,如果你还没有完全理解HOC,别担心,多写几个例子,你就会发现:「哦,原来如此!」的那一刻。毕竟,纸上得来终觉浅,绝知此事要躬行。去吧,打开你的IDE,开始HOC的探索之旅吧!

海码面试 小程序

包含最新面试经验分享,面试真题解析,全栈2000+题目库,前后端面试技术手册详解;无论您是校招还是社招面试还是想提升编程能力,都能从容面对~