组件设计模式(下):HOC、Render Props 与 Compound Components

引言

在 React 开发中,组件复用是核心课题。昨天我们学习了受控/非受控组件和容器组件模式,今天继续深入三种高级设计模式:高阶组件( HOC Render PropsCompound Components。掌握这些模式,能让你写出更灵活、可维护的代码。

一、高阶组件(HOC)

HOC 是接收组件并返回新组件的函数,本质是函数式编程的组合思想。

典型场景:权限控制

javascript 复制代码
// withAuth.jsx
function withAuth(WrappedComponent) {
  return function AuthenticatedComponent(props) {
    const { isAuthenticated, user } = useAuth();
    
    if (!isAuthenticated) {
      return <Navigate to="/login" replace />;
    }
    
    return <WrappedComponent {...props} user={user} />;
  };
}

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

典型场景:日志追踪

javascript 复制代码
// withLogging.jsx
function withLogging(WrappedComponent) {
  return function LoggedComponent(props) {
    useEffect(() => {
      console.log(`[Mount] ${WrappedComponent.name}`, props);
      return () => {
        console.log(`[Unmount] ${WrappedComponent.name}`);
      };
    }, [props]);
    
    return <WrappedComponent {...props} />;
  };
}

⚠️ HOC 注意事项

  1. 不要在 render 中使用 HOC:会导致组件树重建,状态丢失
  2. 静态方法 丢失 :需要用 hoist-non-react-statics 拷贝
  3. ref 传递问题 :需要用 forwardRef 处理

二、Render Props 模式

Render Props 是通过函数 prop 实现代码复用的模式,比 HOC 更灵活。

基础示例:鼠标追踪

javascript 复制代码
// MouseTracker.jsx
function MouseTracker({ children }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  
  useEffect(() => {
    const handleMove = (e) => {
      setPosition({ x: e.clientX, y: e.clientY });
    };
    window.addEventListener('mousemove', handleMove);
    return () => window.removeEventListener('mousemove', handleMove);
  }, []);
  
  return children(position);
}

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

实战:数据请求 Render Props

javascript 复制代码
// Fetch.jsx
function Fetch({ url, children, onError }) {
  const [state, setState] = useState({
    data: null,
    loading: true,
    error: null
  });
  
  useEffect(() => {
    const controller = new AbortController();
    
    fetch(url, { signal: controller.signal })
      .then(res => res.json())
      .then(data => setState({ data, loading: false, error: null }))
      .catch(err => {
        if (err.name !== 'AbortError') {
          setState({ data: null, loading: false, error: err });
          onError?.(err);
        }
      });
    
    return () => controller.abort();
  }, [url]);
  
  return children(state);
}

// 使用
<Fetch url="/api/users">
  {({ data, loading, error }) => {
    if (loading) return <Spinner />;
    if (error) return <ErrorMessage error={error} />;
    return <UserList users={data} />;
  }}
</Fetch>

Render Props vs HOC

维度 HOC Render Props
嵌套层级 易产生包装地狱 更扁平
prop 冲突 可能覆盖原 prop 无冲突
类型推断 较复杂 更直观
使用场景 简单增强 复杂逻辑复用

三、Compound Components 模式

Compound Components 通过上下文实现组件间隐式通信,适合构建复杂 UI 组件。

实战:可组合的 Select 组件

javascript 复制代码
// Select.jsx
const SelectContext = createContext(null);

function Select({ children, value, onChange }) {
  const [open, setOpen] = useState(false);
  
  return (
    <SelectContext.Provider value={{ value, onChange, open, setOpen }}>
      <div className="select-container">{children}</div>
    </SelectContext.Provider>
  );
}

function SelectTrigger({ children }) {
  const { open, setOpen } = useContext(SelectContext);
  return (
    <button onClick={() => setOpen(!open)} className="trigger">
      {children}
    </button>
  );
}

function SelectContent({ children }) {
  const { open } = useContext(SelectContext);
  if (!open) return null;
  return <div className="content">{children}</div>;
}

function SelectOption({ value, children }) {
  const { value: selectedValue, onChange } = useContext(SelectContext);
  const isSelected = value === selectedValue;
  
  return (
    <div 
      className={`option ${isSelected ? 'selected' : ''}`}
      onClick={() => onChange(value)}
    >
      {children}
    </div>
  );
}

// 组合使用
Select.Trigger = SelectTrigger;
Select.Content = SelectContent;
Select.Option = SelectOption;

// 实际使用
<Select value={selected} onChange={setSelected}>
  <Select.Trigger>选择城市</Select.Trigger>
  <Select.Content>
    <SelectOption value="beijing">北京</SelectOption>
    <SelectOption value="shanghai">上海</SelectOption>
    <SelectOption value="guangzhou">广州</SelectOption>
  </Select.Content>
</Select>

优势分析

  1. 声明式 API:使用者只需关注组合,不关心内部实现
  2. 灵活布局:子组件可以任意排列,不受父组件限制
  3. 隐式通信:通过 Context 传递状态,避免 prop drilling

总结

模式 核心思想 适用场景
HOC 函数组合增强 权限、日志、数据注入
Render Props 函数 prop 复用 复杂逻辑、动态渲染
Compound Context 隐式通信 复杂 UI 组件库

选型建议

  • 简单功能增强 → HOC
  • 需要灵活渲染 → Render Props
  • 构建组件库 → Compound Components

React 18 之后,许多场景可以用 Hooks + Context 替代,但理解这些经典模式对阅读老代码和设计复杂组件仍有重要价值。

相关推荐
killerbasd7 小时前
还是迷茫 5.3
前端·react.js·前端框架
江南十四行13 小时前
ReAct Agent 基本理论与项目实战(一)
前端·react.js·前端框架
谢尔登16 小时前
10_从 React Hooks 本质看 useState
前端·ubuntu·react.js
辰同学ovo16 小时前
从全局登录状态管理学习 Redux
前端·javascript·学习·react.js
光影少年17 小时前
reeact虚拟DOM、Diff算法原理、key的作用与为什么不能用index
前端·react.js·掘金·金石计划
江南十四行18 小时前
ReAct Agent 基本理论与项目实战(二)
前端·react.js·前端框架
摘星编程19 小时前
当AI开始学会“使用工具“——从ReAct到MCP,大模型如何获得真正的行动力
前端·人工智能·react.js
啊哈一半醒21 小时前
React 核心知识点系统总结:从基础语法到高级 API,一篇文章梳理完整学习路线
javascript·学习·react.js
cn_mengbei2 天前
用React Native开发OpenHarmony应用:Reanimated共享元素过渡
javascript·react native·react.js
We་ct3 天前
React 性能优化精讲
前端·javascript·react.js·性能优化·前端框架·html·浏览器