高阶组件 #

一、高阶组件概述 #

1.1 什么是高阶组件 #

高阶组件(Higher-Order Component,HOC)是一个函数,接收一个组件并返回一个新组件。

javascript
const EnhancedComponent = higherOrderComponent(WrappedComponent);

1.2 类比理解 #

text
高阶函数:接收函数,返回函数
高阶组件:接收组件,返回组件

1.3 使用场景 #

场景 说明
代码复用 多个组件共享逻辑
权限控制 检查用户权限
数据获取 封装数据请求逻辑
日志记录 记录组件行为
性能监控 测量渲染性能

二、基本实现 #

2.1 基本结构 #

javascript
function withEnhancement(WrappedComponent) {
  return function EnhancedComponent(props) {
    return <WrappedComponent {...props} />;
  };
}

// 使用箭头函数
const withEnhancement = (WrappedComponent) => {
  return (props) => <WrappedComponent {...props} />;
};

2.2 添加新功能 #

javascript
function withLogger(WrappedComponent) {
  return function WithLogger(props) {
    useEffect(() => {
      console.log(`${WrappedComponent.name} mounted`);
      return () => {
        console.log(`${WrappedComponent.name} unmounted`);
      };
    }, []);

    return <WrappedComponent {...props} />;
  };
}

// 使用
const EnhancedButton = withLogger(Button);

2.3 操作Props #

javascript
function withDefaultProps(defaultProps) {
  return function (WrappedComponent) {
    return function EnhancedComponent(props) {
      return <WrappedComponent {...defaultProps} {...props} />;
    };
  };
}

// 使用
const ButtonWithDefaults = withDefaultProps({
  type: 'button',
  className: 'btn'
})(Button);

三、常见HOC模式 #

3.1 属性代理 #

javascript
function withLoading(WrappedComponent) {
  return function WithLoading({ isLoading, ...props }) {
    if (isLoading) {
      return <div>Loading...</div>;
    }
    return <WrappedComponent {...props} />;
  };
}

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

function App() {
  const [loading, setLoading] = useState(true);
  const [users, setUsers] = useState([]);

  return <UserListWithLoading isLoading={loading} users={users} />;
}

3.2 反向继承 #

javascript
function withLogging(WrappedComponent) {
  return class extends WrappedComponent {
    componentDidMount() {
      console.log('Component mounted');
      super.componentDidMount?.();
    }

    render() {
      console.log('Component rendering');
      return super.render();
    }
  };
}

3.3 条件渲染 #

javascript
function withAuth(WrappedComponent) {
  return function WithAuth(props) {
    const { user } = useAuth();

    if (!user) {
      return <LoginPage />;
    }

    return <WrappedComponent {...props} user={user} />;
  };
}

// 使用
const ProtectedDashboard = withAuth(Dashboard);

3.4 数据获取 #

javascript
function withFetch(url) {
  return function (WrappedComponent) {
    return function WithFetch(props) {
      const [data, setData] = useState(null);
      const [loading, setLoading] = useState(true);
      const [error, setError] = useState(null);

      useEffect(() => {
        fetch(url)
          .then(res => res.json())
          .then(setData)
          .catch(setError)
          .finally(() => setLoading(false));
      }, [url]);

      return (
        <WrappedComponent
          {...props}
          data={data}
          loading={loading}
          error={error}
        />
      );
    };
  };
}

// 使用
const UserListWithData = withFetch('/api/users')(UserList);

四、组合HOC #

4.1 嵌套组合 #

javascript
const EnhancedComponent = withAuth(
  withLoading(
    withLogger(Component)
  )
);

4.2 compose函数 #

javascript
function compose(...funcs) {
  return funcs.reduce((a, b) => (...args) => a(b(...args)));
}

// 使用
const enhance = compose(
  withAuth,
  withLoading,
  withLogger
);

const EnhancedComponent = enhance(Component);

4.3 使用库 #

javascript
import { compose } from 'redux';
// 或
import { flowRight } from 'lodash';

const enhance = compose(
  withAuth,
  withLoading,
  withLogger
);

五、转发Ref #

5.1 问题 #

javascript
function withEnhancement(WrappedComponent) {
  return function EnhancedComponent(props) {
    return <WrappedComponent {...props} />;
  };
}

// ❌ ref会指向EnhancedComponent,而不是WrappedComponent
const EnhancedButton = withEnhancement(Button);

5.2 解决方案 #

javascript
function withEnhancement(WrappedComponent) {
  const EnhancedComponent = forwardRef((props, ref) => {
    return <WrappedComponent {...props} ref={ref} />;
  });

  EnhancedComponent.displayName = `withEnhancement(${WrappedComponent.displayName || WrappedComponent.name})`;
  
  return EnhancedComponent;
}

六、最佳实践 #

6.1 设置displayName #

javascript
function withHOC(WrappedComponent) {
  function WithHOC(props) {
    return <WrappedComponent {...props} />;
  }

  WithHOC.displayName = `withHOC(${WrappedComponent.displayName || WrappedComponent.name})`;

  return WithHOC;
}

6.2 传递不相关的Props #

javascript
function withHOC(WrappedComponent) {
  return function WithHOC({ specialProp, ...passThroughProps }) {
    // 处理specialProp
    const enhancedProps = {
      ...passThroughProps,
      // 添加新的props
    };

    return <WrappedComponent {...enhancedProps} />;
  };
}

6.3 复制静态方法 #

javascript
import hoistNonReactStatics from 'hoist-non-react-statics';

function withHOC(WrappedComponent) {
  function WithHOC(props) {
    return <WrappedComponent {...props} />;
  }

  hoistNonReactStatics(WithHOC, WrappedComponent);

  return WithHOC;
}

6.4 不要在渲染中使用HOC #

javascript
// ❌ 错误:每次渲染都创建新组件
function Component() {
  const EnhancedComponent = withHOC(Button);
  return <EnhancedComponent />;
}

// ✅ 正确:在组件外部创建
const EnhancedButton = withHOC(Button);

function Component() {
  return <EnhancedButton />;
}

6.5 复制方法到类组件 #

javascript
function withHOC(WrappedComponent) {
  class WithHOC extends React.Component {
    static displayName = `withHOC(${WrappedComponent.displayName || WrappedComponent.name})`;

    static WrappedComponent = WrappedComponent;

    render() {
      return <WrappedComponent {...this.props} />;
    }
  }

  return hoistNonReactStatics(WithHOC, WrappedComponent);
}

七、HOC vs Hooks #

7.1 对比 #

特性 HOC Hooks
代码量 较多 较少
嵌套问题 存在 不存在
可读性 较差 较好
TypeScript 复杂 简单
推荐程度 旧代码维护 新项目推荐

7.2 HOC改写为Hooks #

javascript
// HOC方式
function withWindowSize(WrappedComponent) {
  return function WithWindowSize(props) {
    const [size, setSize] = useState({
      width: window.innerWidth,
      height: window.innerHeight
    });

    useEffect(() => {
      const handleResize = () => {
        setSize({
          width: window.innerWidth,
          height: window.innerHeight
        });
      };

      window.addEventListener('resize', handleResize);
      return () => window.removeEventListener('resize', handleResize);
    }, []);

    return <WrappedComponent {...props} windowSize={size} />;
  };
}

// Hooks方式
function useWindowSize() {
  const [size, setSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });

  useEffect(() => {
    const handleResize = () => {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    };

    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return size;
}

// 使用
function Component() {
  const windowSize = useWindowSize();
  return <div>{windowSize.width} x {windowSize.height}</div>;
}

八、实际案例 #

8.1 权限控制HOC #

javascript
function withPermission(permission) {
  return function (WrappedComponent) {
    return function WithPermission(props) {
      const { user } = useAuth();

      if (!user?.permissions?.includes(permission)) {
        return <AccessDenied />;
      }

      return <WrappedComponent {...props} />;
    };
  };
}

// 使用
const AdminPanel = withPermission('admin')(Panel);

8.2 性能监控HOC #

javascript
function withPerformance(WrappedComponent) {
  return function WithPerformance(props) {
    useEffect(() => {
      const startTime = performance.now();
      
      return () => {
        const endTime = performance.now();
        console.log(`${WrappedComponent.name} rendered in ${endTime - startTime}ms`);
      };
    });

    return <WrappedComponent {...props} />;
  };
}

8.3 错误边界HOC #

javascript
function withErrorBoundary(WrappedComponent) {
  return class WithErrorBoundary extends React.Component {
    state = { hasError: false, error: null };

    static getDerivedStateFromError(error) {
      return { hasError: true, error };
    }

    componentDidCatch(error, errorInfo) {
      console.error('Error:', error, errorInfo);
    }

    render() {
      if (this.state.hasError) {
        return <ErrorFallback error={this.state.error} />;
      }

      return <WrappedComponent {...this.props} />;
    }
  };
}

九、总结 #

要点 说明
定义 接收组件返回组件的函数
模式 属性代理、反向继承
组合 使用compose函数
注意 设置displayName、转发ref

核心原则:

  • HOC 用于复用组件逻辑
  • 不要在渲染中创建 HOC
  • 设置 displayName 便于调试
  • 新项目优先使用 Hooks
最后更新:2026-03-26