---
title: 设计文档
order: 2
category: pro
---

# PisellDataSourceContainer 设计文档

## 设计思路

PisellDataSourceContainer 是一个面向数据驱动应用的容器组件，旨在解决后台管理系统中列表页面的通用痛点：

### 核心痛点

1. **数据操作重复**: 每个列表页面都需要编写相似的 CRUD 逻辑
2. **状态管理复杂**: 分页、筛选、排序等状态散落各处，难以维护
3. **组件协调困难**: Filter、Table、Pagination 等组件之间需要频繁通信
4. **代码冗余**: 相似的数据请求、错误处理、加载状态逻辑重复编写
5. **跨组件通信**: 缺少统一的状态共享机制

### 解决方案

PisellDataSourceContainer 通过 **Provider 模式** 和 **Context API** 构建了一个多层次的数据和状态管理体系：

```
┌─────────────────────────────────────────────────────┐
│         PisellDataSourceContainer                   │
│                                                     │
│  ┌──────────────────────────────────────────────┐  │
│  │        DataSourceProvider                    │  │
│  │  (数据源配置、数据请求)                        │  │
│  │                                              │  │
│  │  ┌────────────────────────────────────────┐ │  │
│  │  │     RefManagerProvider                 │ │  │
│  │  │  (Ref 管理、SharedState)                │ │  │
│  │  │                                        │ │  │
│  │  │  ┌──────────────────────────────────┐ │ │  │
│  │  │  │  ChildComponentPropsProvider     │ │ │  │
│  │  │  │  (子组件属性管理、Form 实例)       │ │ │  │
│  │  │  │                                  │ │ │  │
│  │  │  │  ┌────────────────────────────┐ │ │ │  │
│  │  │  │  │  HandleActionsProvider     │ │ │ │  │
│  │  │  │  │  (操作方法封装)              │ │ │ │  │
│  │  │  │  │                            │ │ │ │  │
│  │  │  │  │    children (业务组件)      │ │ │ │  │
│  │  │  │  │                            │ │ │ │  │
│  │  │  │  └────────────────────────────┘ │ │ │  │
│  │  │  └──────────────────────────────────┘ │ │  │
│  │  └────────────────────────────────────────┘ │  │
│  └──────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘
```

## 架构设计

### 1. DataSourceProvider（数据源层）

**职责**：管理数据源配置和基础数据请求能力

**提供能力**：

- 数据源配置存储
- 基础 CRUD 操作（list、get、create、update、destroy）
- 数据覆盖（overrideData）
- 额外参数（extraParams）
- 设计模式支持

**实现要点**：

```
interface DataSourceContextProps {
  dataSource: string | DataSourceConfig; // 数据源配置
  overrideData?: any; // 数据覆盖
  actions?: ActionsConfig; // 操作配置
  currentValue?: any; // 当前值
  __designMode?: boolean; // 设计模式
  extraParams?: Record<string, any>; // 额外参数
}
```

**设计理念**：

- 提供最原始的数据操作能力
- 不关心业务逻辑和状态管理
- 作为其他 Provider 的基础依赖

### 2. RefManagerProvider（引用管理层）

**职责**：管理组件引用和共享状态

**提供能力**：

- **Ref 管理**：注册、获取、注销组件引用
- **SharedState 管理**：跨组件状态共享和订阅机制

**核心实现**：

```
interface RefManagerContextType {
  // ========== Ref 管理 ==========
  getRef: <T = any>(id: string) => T | undefined;
  registerRef: <T = any>(id: string, ref: T) => void;
  unregisterRef: (id: string) => void;

  // ========== SharedState 管理 ==========
  getSharedState: <T = any>(key: string) => T | undefined;
  setSharedState: <T = any>(key: string, value: T) => void;
  subscribeSharedState: <T = any>(
    key: string,
    callback: (value: T) => void
  ) => () => void; // 返回取消订阅函数
}
```

**设计理念**：

- **Ref 管理**：解决跨组件调用组件方法的问题
  - 场景：父组件需要调用子组件的方法（如表格的刷新方法）
  - 优势：无需 props drilling，直接通过 id 获取引用
- **SharedState 管理**：解决跨组件状态共享问题
  - 场景：多个组件需要共享某个状态（如选中的行、当前的筛选条件）
  - 优势：发布-订阅模式，自动通知所有订阅者
  - 对比：比 Redux 更轻量，比 props 更灵活

**实现细节**：

```
// 使用 Map 存储引用和状态
const refMapRef = useRef<Map<string, any>>(new Map());
const sharedStateRef = useRef<Map<string, any>>(new Map());
const subscribersRef = useRef<Map<string, Set<(value: any) => void>>>(new Map());

// 发布-订阅机制
const setSharedState = (key: string, value: any) => {
  sharedStateRef.current.set(key, value);

  // 通知所有订阅者
  const subscribers = subscribersRef.current.get(key);
  if (subscribers) {
    subscribers.forEach(callback => callback(value));
  }
};
```

### 3. ChildComponentPropsProvider（子组件属性层）

**职责**：统一管理子组件的属性配置

**提供能力**：

- Form 实例管理（内置 Ant Design Form）
- 子组件属性集中配置
- 子组件属性按需分发

**核心类型**：

```
interface ChildComponentPropsType {
  filterComponent?: {
    value: {
      quickFilter?: FilterItemProps[];
      otherFilter?: FilterItemProps[];
    };
    formFiltersPrefix?: string;
    quickFilterMaxLength?: number;
  };
  tableComponent?: AntdTableProps<any>;
  paginationComponent?: PaginationProps;
  sortComponent?: SortListProps;
  columnsSettingComponent?: {
    value?: Record<string, any>[];
    onChange?: (val: Record<string, any>[]) => void;
  };
  // ... 更多子组件配置
}
```

**设计理念**：

- **集中式配置**：所有子组件的属性都在容器层配置，避免 props drilling
- **按需分发**：通过 `useContainerContext(displayName)` 获取特定组件的属性
- **Form 集成**：内置 Form 实例，子组件可以直接使用
- **解耦设计**：子组件不需要关心数据来源，只需要声明需要什么属性

**使用示例**：

```
// 在容器层配置
<PisellDataSourceContainer
  childComponentProps={{
    tableComponent: {
      columns: [...],
      rowSelection: {...}
    },
    paginationComponent: {
      showSizeChanger: true
    }
  }}
>
  <MyTable />
  <MyPagination />
</PisellDataSourceContainer>

// 在子组件中获取
const MyTable = () => {
  const context = useContainerContext('tableComponent');
  // context 会自动包含 tableComponent 的配置
  const { columns, rowSelection, serverActions, form } = context;
  // ...
};
```

### 4. HandleActionsProvider（操作处理层）

**职责**：封装和增强数据操作方法

**提供能力**：

- 扩展 list 操作（添加 onSearch、onReset、onPageChange）
- 分页状态管理
- 表单联动（表单值变化自动触发查询）
- 自动执行查询（autoRun）

**核心扩展**：

```
interface HandleActionContextType {
  list: Result<any, any> & {
    onSearch: (newParams?: Record<string, any>) => void;  // 查询（自动重置到第一页）
    onReset: () => void;  // 重置
    onPageChange: (pageNumber: number, pageSize: number) => void;  // 分页切换
    pagination?: {
      pageNumber: number;
      pageSize: number;
    };
  };
  get: Result<any, any>;
  create: Result<any, any>;
  update: Result<any, any>;
  destroy: Result<any, any>;
  getCurrentRecord: () => any;
}
```

**设计理念**：

#### onSearch 方法设计

```
const onSearch = (newParams?: Record<string, any>) => {
  // 合并参数优先级：
  // 1. newParams（新传入的参数，优先级最高）
  // 2. initialParams（初始化时的参数）
  // 3. params（上一次的参数）
  let searchParams = {
    ...(params?.[0] || {}),
    ...initialParams,
    ...newParams,
  };

  // 如果启用分页，自动重置到第一页
  if (enablePagination) {
    searchParams = {
      ...searchParams,
      pageNumber: 1,
      pageSize: pagination.pageSize,
    };
  }

  run(searchParams);
};
```

**设计考虑**：

- 参数合并策略：确保新参数能覆盖旧参数
- 自动重置分页：搜索时应该回到第一页
- 保留其他参数：排序、筛选等参数应该保留

#### onReset 方法设计

```
const onReset = () => {
  // 重置到初始参数和初始分页
  let resetParams = { ...initialParams };

  if (enablePagination) {
    setPagination({
      pageNumber: pageNumber ?? 1,
      pageSize: pageSize ?? 10,
    });
    resetParams = {
      ...resetParams,
      pageNumber: pageNumber ?? 1,
      pageSize: pageSize ?? 10,
    };
  }

  run(resetParams);
};
```

**设计考虑**：

- 完全重置：清除所有查询条件
- 恢复初始状态：分页、筛选等恢复到初始值
- 自动触发查询：重置后立即查询

#### onPageChange 方法设计

```
const onPageChange = (pageNumber: number, pageSize: number) => {
  if (!enablePagination) {
    console.warn('分页功能未启用');
    return;
  }

  setPagination({ pageNumber, pageSize });

  // 保留上一次的查询参数
  const lastParams = params?.[0] || { ...initialParams };
  run({
    ...lastParams,
    pageNumber,
    pageSize,
  });
};
```

**设计考虑**：

- 保留查询条件：分页切换时不应该清空筛选条件
- 同步分页状态：更新内部分页状态
- 支持 pageSize 变化：支持每页显示数量的切换

#### 自动执行逻辑

```
useEffect(() => {
  if (autoRun?.list) {
    const formValues = form.getFieldsValue();
    if (dataSourceKey) {
      handleAction.list.onSearch(formValues);
    } else {
      handleAction.list.mutate([]); // 无数据源时返回空数组
    }
  }
}, [dataSourceKey, autoRun?.list]);
```

**设计考虑**：

- 初始化查询：组件挂载时自动查询
- 表单联动：自动获取表单值作为查询参数
- 数据源切换：当数据源变化时重新查询

### 5. useContainerContext Hook（上下文聚合层）

**职责**：聚合所有 Provider 提供的能力，按需分发给子组件

**核心实现**：

```
const useContainerContext = (displayName?: string) => {
  const handleActionsValue = useHandleActionProvider();
  const refManager = useRefManagerProvider();
  const childComponentProps = useChildComponentPropsContext();

  // 表单值变化回调
  const onValuesChange = useCallback((changedValues, allValues) => {
    handleActionsValue.list.onSearch(changedValues);
  }, [handleActionsValue.list]);

  return useMemo(() => {
    const { form, ...childProps } = childComponentProps;

    let contextValue = {
      serverActions: handleActionsValue,
      refManager,
      form,
      onValuesChange,
    };

    // 如果指定了 displayName，注入对应的子组件属性
    if (displayName && childProps) {
      const specificProps = childProps[displayName];
      if (specificProps) {
        contextValue = {
          ...contextValue,
          ...specificProps
        };
      }
    }

    return contextValue;
  }, [displayName, onValuesChange, handleActionsValue, refManager, childComponentProps]);
};
```

**设计理念**：

- **按需获取**：通过 displayName 参数指定要获取哪个子组件的属性
- **自动注入**：将 serverActions、refManager、form 等自动注入
- **性能优化**：使用 useMemo 避免不必要的重新计算
- **灵活扩展**：可以轻松添加新的上下文能力

**使用场景**：

```
// 场景 1：获取基础能力
const { serverActions, refManager, form } = useContainerContext();

// 场景 2：获取特定子组件的属性
const MyTable = () => {
  // columns 会被自动注入
  const { columns, serverActions, form } = useContainerContext('tableComponent');
  // ...
};
```

## withContainer HOC 设计

**职责**：将普通组件包装为容器感知组件

**核心实现**：

```
function withContainer<P extends object, R = any>(
  WrappedComponent: ComponentType<P & UseContainerContextReturnType>,
  displayName: string
) {
  return forwardRef<R, P & { refId?: string }>((props, forwardedRef) => {
    const { refId, ...otherProps } = props;
    const containerContext = useContainerContext(displayName);
    const innerRef = useRef<R>(null);

    // 自动转发 ref
    useImperativeHandle(forwardedRef, () => innerRef.current as R, []);

    // 自动注册到 RefManager
    useEffect(() => {
      if (refId && innerRef.current) {
        containerContext.refManager.registerRef(refId, innerRef.current);
      }
      return () => {
        if (refId) {
          containerContext.refManager.unregisterRef(refId);
        }
      };
    }, [refId, containerContext.refManager]);

    // 合并 props
    const mergedProps = useMemo(() => ({
      ...containerContext,
      ...otherProps,
    }), [otherProps, containerContext]);

    return <WrappedComponent ref={innerRef} {...mergedProps} />;
  });
}
```

**设计理念**：

- **自动注入上下文**：无需手动调用 useContainerContext
- **自动管理 ref**：支持 refId 属性，自动注册和注销
- **保持灵活性**：仍然支持传入额外的 props
- **类型安全**：完整的 TypeScript 类型支持

**使用示例**：

```
// 定义组件
const MyTableComponent = ({ serverActions, columns, ...props }) => {
  return <Table dataSource={serverActions.list.data.list} columns={columns} />;
};

// 包装组件
const MyTable = withContainer(MyTableComponent, 'tableComponent');

// 使用组件
<PisellDataSourceContainer
  childComponentProps={{
    tableComponent: {
      columns: [...]
    }
  }}
>
  <MyTable refId="myTable" />
</PisellDataSourceContainer>

// 在其他地方调用
const { refManager } = useContainerContext();
const tableRef = refManager.getRef('myTable');
tableRef.refresh?.();
```

## 数据流设计

### 查询流程

```
用户操作
  ↓
Form.onValuesChange
  ↓
useContainerContext.onValuesChange
  ↓
handleActions.list.onSearch
  ↓
合并参数 + 分页处理
  ↓
dataSource.list.run
  ↓
API 请求
  ↓
formatListResult
  ↓
更新 list.data
  ↓
子组件自动更新
```

### 分页流程

```
用户点击分页
  ↓
Pagination.onChange
  ↓
handleActions.list.onPageChange
  ↓
更新 pagination 状态
  ↓
保留查询参数 + 更新分页参数
  ↓
dataSource.list.run
  ↓
API 请求
  ↓
更新 list.data
```

### 状态共享流程

```
组件 A: setSharedState('key', value)
  ↓
更新 sharedStateRef.current
  ↓
遍历 subscribersRef.current.get('key')
  ↓
调用所有订阅者的 callback(value)
  ↓
组件 B、C、D 自动更新
```

## 设计优势

### 1. 关注点分离

- **数据层**：DataSourceProvider 只关心数据请求
- **引用层**：RefManagerProvider 只关心引用和状态管理
- **属性层**：ChildComponentPropsProvider 只关心属性分发
- **操作层**：HandleActionsProvider 只关心操作封装

### 2. 高内聚低耦合

- 每个 Provider 职责单一
- Provider 之间通过 Context 通信
- 子组件只依赖 useContainerContext

### 3. 易于扩展

- 添加新的 Provider：在嵌套层级中插入即可
- 添加新的子组件配置：在 ChildComponentPropsType 中添加类型
- 添加新的操作方法：在 HandleActionContextType 中扩展

### 4. 类型安全

- 完整的 TypeScript 类型定义
- 泛型支持（getRef、setSharedState）
- 编译时类型检查

### 5. 性能优化

- useMemo 缓存计算结果
- useCallback 缓存回调函数
- 发布-订阅模式，精确更新

## 设计权衡

### 1. Provider 嵌套层级

**权衡**：4 层 Provider 嵌套

- **优势**：职责清晰，易于理解和维护
- **劣势**：嵌套较深，可能影响性能
- **结论**：通过 useMemo 优化性能，可接受

### 2. SharedState vs Redux

**权衡**：使用内置 SharedState 而非 Redux

- **优势**：轻量、简单、无需额外依赖
- **劣势**：功能不如 Redux 强大
- **结论**：对于容器内的状态共享已足够

### 3. Form 内置 vs 外部传入

**权衡**：在 ChildComponentPropsProvider 中内置 Form

- **优势**：开箱即用，自动联动
- **劣势**：可能与外部 Form 冲突
- **结论**：大部分场景下内置 Form 更便捷

### 4. 自动查询 vs 手动查询

**权衡**：提供 autoRun 配置

- **优势**：减少模板代码
- **劣势**：可能造成不必要的请求
- **结论**：默认不自动执行，由业务决定

## 未来规划

### 1. 插件机制

支持通过插件扩展容器能力：

```
<PisellDataSourceContainer
  plugins={[
    cachePlugin,
    logPlugin,
    retryPlugin,
  ]}
>
```

### 2. 更多内置子组件

- 批量操作栏
- 导出组件
- 更多筛选器类型

### 3. 性能监控

- 请求耗时统计
- 渲染性能分析
- 状态变化追踪

### 4. DevTools

- 可视化容器状态
- 时间旅行调试
- 操作记录回放

## 总结

PisellDataSourceContainer 通过分层设计和 Provider 模式，构建了一个强大而灵活的数据容器组件。它解决了列表页面开发中的常见痛点，提供了统一的数据管理、操作处理、状态共享和子组件协调能力。

核心设计理念：

1. **分层解耦**：每层职责单一，易于维护
2. **渐进增强**：从基础能力到高级功能层层递进
3. **开箱即用**：提供合理的默认配置
4. **灵活扩展**：支持自定义和扩展
