数据获取能力的注入式设计与实现

95 阅读4分钟

从组合与继承的角度来看,“获取数据的能力”更适合设计为可组合的独立模块(而非组件或属性),或通过组合方式嵌入组件,而非通过继承实现。以下是具体分析:

一、为什么不适合设计为“属性”?

属性(props)的核心作用是“传递配置或简单数据”,而“获取数据”是行为逻辑(如调用接口、处理加载/错误状态、缓存数据等),若用属性承载会存在明显问题:

  • 属性本质是“静态输入”,无法封装复杂逻辑(如异步请求、重试机制),若强行通过 onFetch 等回调属性让父组件传入,会导致逻辑分散,违背“高内聚”原则。
  • 示例:若给 Table 组件加 fetchUrl 属性来获取数据,Table 需内置请求逻辑,但若其他组件(如 List)也需要同样的请求能力,会导致代码重复,且难以统一维护(如统一加请求拦截器)。

二、为什么不适合用“继承”实现?

若通过继承让组件获得数据获取能力(如定义 FetchComponent 父类,让 TableList 继承它),会存在以下问题:

  • 继承的“强耦合”限制灵活性:继承是“is-a”关系,Table 继承 FetchComponent 意味着“Table 本质是一种具备获取数据能力的组件”,但实际 Table 的核心职责是“展示数据”,获取数据只是附加能力(可能存在无需获取数据的 Table,如静态数据表格)。
  • 逻辑复用受限:若后续需要给非组件类(如工具函数)或第三方组件添加数据获取能力,继承完全无法支持,而组合则可以。

三、更优解:用“组合”实现数据获取能力

“获取数据”是跨组件的通用能力,适合设计为独立模块(如 Hooks、高阶组件、或专门的数据服务),再通过组合方式与业务组件结合,核心逻辑是“能力注入”而非“继承绑定”。

1. 用 Hooks 组合(推荐,适合 React 等框架)

  • 设计 useFetch 这样的自定义 Hooks,封装请求逻辑(url、参数、加载/错误状态):

    // 数据获取模块(独立)
    function useFetch(url) {
      const [data, setData] = useState(null);
      const [loading, setLoading] = useState(false);
      // 实现请求逻辑...
      return {
        data,
        loading,
        fetch: () => {
          /* 触发请求 */
        },
      };
    }
    
    // 业务组件通过组合使用
    function DataTable({ url }) {
      const { data, loading } = useFetch(url); // 组合数据能力
      return <Table dataSource={data} loading={loading} />; // 组合表格组件
    }
    
    • 逻辑:useFetch 是独立的能力模块,DataTable 通过“引入+调用”的方式组合其能力,两者无继承关系,useFetch 还可被 ListCard 等其他组件复用。

2. 用高阶组件(HOC)组合

  • 定义 withFetch 高阶组件,给目标组件注入数据获取能力:

    function withFetch(WrappedComponent) {
      return (props) => {
        const { data, loading } = useFetch(props.url); // 内部封装请求
        return <WrappedComponent {...props} data={data} loading={loading} />;
      };
    }
    
    // 给 Table 组件组合数据能力
    const DataTable = withFetch(Table);
    
    • 逻辑:通过“包装”而非“继承”,让 Table 获得数据能力,Table 本身仍保持纯净(只负责展示),且 withFetch 可作用于任何需要数据的组件。

3. 用容器组件组合(分离 UI 与数据逻辑)

  • 拆分为“容器组件(处理数据)”和“UI 组件(展示)”:

    // 容器组件:负责数据获取(组合数据能力)
    function TableContainer({ url }) {
      const { data, loading } = useFetch(url);
      return <TableUI data={data} loading={loading} />; // 组合UI组件
    }
    
    // UI组件:纯展示,无数据逻辑
    function TableUI({ data, loading }) {
      return <Table dataSource={data} loading={loading} />;
    }
    
    • 逻辑:数据逻辑与 UI 逻辑完全分离,通过组合协作,各自可独立修改(如换用 ListUI 展示数据,只需修改容器组件的组合对象)。

四、总结:组合的核心优势

  • 灵活性:数据获取能力可独立演化(如新增缓存、重试逻辑),不影响使用它的组件;
  • 复用性:同一数据模块可被任意组件组合使用,无需重复开发;
  • 低耦合:业务组件(如 Table)与数据能力模块相互独立,职责清晰(符合单一职责原则)。

而继承或属性的方式,要么会导致逻辑耦合,要么无法满足复杂能力的复用需求,因此组合是实现“获取数据能力”的最优解