---
title: 开发文档
order: 3
category: pro
---

# PisellDataSourceContainer 开发文档

## 项目结构

```
packages/materials/src/components/pisellDataSourceContainer/
├── index.ts                                  # 主入口
├── PisellDataSourceContainer.tsx             # 容器组件主文件
├── type.ts                                   # TypeScript 类型定义
├── provider/                                 # Provider 层
│   ├── ChildComponentProps.tsx               # 子组件属性 Provider
│   ├── HandleActions.tsx                     # 操作处理 Provider
│   └── RefManager.tsx                        # 引用管理 Provider
├── hooks/                                    # Hooks
│   ├── useContainerContext.ts                # 容器上下文 Hook
│   ├── useDataSourceKey.ts                   # 数据源键值 Hook
│   └── useHandleAction.ts                    # 操作处理 Hook
├── utils/                                    # 工具函数
│   ├── formatData.ts                         # 数据格式化
│   └── withContainer.tsx                     # HOC 包装器
├── components/                               # 内置子组件
│   ├── Filter/                               # 筛选组件
│   ├── Table/                                # 表格组件
│   ├── Pagination/                           # 分页组件
│   ├── Sort/                                 # 排序组件
│   └── ColumnsSetting/                       # 列设置组件
└── docs/                                     # 文档目录
    ├── PisellDataSourceContainer.md
    ├── PisellDataSourceContainer.$tab-design.md
    ├── PisellDataSourceContainer.$tab-dev.md
    └── PisellDataSourceContainer.$tab-test.md
```

## 核心实现

### 1. PisellDataSourceContainer 主组件

位置：`packages/materials/src/components/pisellDataSourceContainer/PisellDataSourceContainer.tsx`

```
const PisellDataSourceContainer = (props: DataSourceContainerProps) => {
  const {
    dataSource,
    __designMode,
    extraParams,
    actions,
    overrideData,
    currentValue,
    children,
    autoRun,
    pagination = true,
    childComponentProps,
  } = props;

  return (
    <DataSourceProvider
      dataSource={dataSource}
      overrideData={overrideData}
      actions={actions}
      currentValue={currentValue}
      __designMode={__designMode}
      extraParams={extraParams}
    >
      <RefManagerProvider>
        <ChildComponentPropsProvider childComponentProps={childComponentProps}>
          <HandleActionsProvider
            autoRun={autoRun || { list: true }}
            dataSource={dataSource}
            pagination={pagination}
            extraParams={extraParams}
          >
            {children}
          </HandleActionsProvider>
        </ChildComponentPropsProvider>
      </RefManagerProvider>
    </DataSourceProvider>
  );
};
```

**实现要点**：

- 4 层 Provider 嵌套，从外到内依次是：DataSourceProvider → RefManagerProvider → ChildComponentPropsProvider → HandleActionsProvider
- 每层 Provider 负责特定的职责
- 通过 props 透传配置到各个 Provider

### 2. RefManagerProvider 实现

位置：`packages/materials/src/components/pisellDataSourceContainer/provider/RefManager.tsx`

#### Ref 管理实现

```
const RefManagerProvider: React.FC<RefManagerProviderProps> = ({ children }) => {
  const refMapRef = useRef<Map<string, any>>(new Map());

  const registerRef = useCallback(<T = any,>(id: string, ref: T) => {
    if (!id) {
      console.warn('[RefManager] registerRef: id 不能为空');
      return;
    }
    refMapRef.current.set(id, ref);
  }, []);

  const getRef = useCallback(<T = any,>(id: string): T | undefined => {
    if (!id) {
      console.warn('[RefManager] getRef: id 不能为空');
      return undefined;
    }
    const ref = refMapRef.current.get(id);
    if (!ref) {
      console.warn(`[RefManager] 组件 "${id}" 未注册`);
    }
    return ref;
  }, []);

  const unregisterRef = useCallback((id: string) => {
    if (!id) {
      console.warn('[RefManager] unregisterRef: id 不能为空');
      return;
    }
    refMapRef.current.delete(id);
  }, []);

  // ... SharedState 实现
};
```

**实现要点**：

- 使用 `useRef<Map>` 存储引用，避免重新渲染
- 使用 `useCallback` 缓存方法，避免子组件不必要的重新渲染
- 完善的错误提示和边界处理

#### SharedState 实现

```
const RefManagerProvider: React.FC<RefManagerProviderProps> = ({ children }) => {
  const sharedStateRef = useRef<Map<string, any>>(new Map());
  const subscribersRef = useRef<Map<string, Set<(value: any) => void>>>(new Map());

  const getSharedState = useCallback(<T = any,>(key: string): T | undefined => {
    if (!key) {
      console.warn('[RefManager] getSharedState: key 不能为空');
      return undefined;
    }
    return sharedStateRef.current.get(key);
  }, []);

  const setSharedState = useCallback(<T = any,>(key: string, value: T) => {
    if (!key) {
      console.warn('[RefManager] setSharedState: key 不能为空');
      return;
    }

    // 更新状态
    sharedStateRef.current.set(key, value);

    // 通知所有订阅者
    const subscribers = subscribersRef.current.get(key);
    if (subscribers && subscribers.size > 0) {
      subscribers.forEach((callback) => {
        try {
          callback(value);
        } catch (error) {
          console.error(`[RefManager] 订阅回调执行失败 (key: ${key}):`, error);
        }
      });
    }
  }, []);

  const subscribeSharedState = useCallback(
    <T = any,>(key: string, callback: (value: T) => void): (() => void) => {
      if (!key || !callback) {
        console.warn('[RefManager] subscribeSharedState: key 和 callback 不能为空');
        return () => {};
      }

      // 创建订阅集合
      if (!subscribersRef.current.has(key)) {
        subscribersRef.current.set(key, new Set());
      }

      const subscribers = subscribersRef.current.get(key)!;
      subscribers.add(callback);

      // 立即触发一次（如果状态已存在）
      const currentState = sharedStateRef.current.get(key);
      if (currentState !== undefined) {
        try {
          callback(currentState);
        } catch (error) {}
      }

      // 返回取消订阅函数
      return () => {
        subscribers.delete(callback);
        if (subscribers.size === 0) {
          subscribersRef.current.delete(key);
        }
      };
    },
    []
  );

  // ... 组装 contextValue
};
```

**实现要点**：

- **发布-订阅模式**：当状态改变时自动通知所有订阅者
- **立即触发**：订阅时如果状态已存在，立即触发回调（避免遗漏初始值）
- **自动清理**：返回取消订阅函数，当订阅者为 0 时清理资源
- **错误处理**：try-catch 包裹回调执行，避免单个订阅者出错影响其他订阅者

### 3. ChildComponentPropsProvider 实现

位置：`packages/materials/src/components/pisellDataSourceContainer/provider/ChildComponentProps.tsx`

```
export const ChildComponentPropsProvider: React.FC<ChildComponentPropsProviderProps> = ({
  childComponentProps,
  children,
}) => {
  const [form] = Form.useForm();

  const contextValue = useMemo(() => {
    return { ...childComponentProps, form };
  }, [childComponentProps, form]);

  return (
    <ChildComponentPropsContext.Provider value={contextValue}>
      {children}
    </ChildComponentPropsContext.Provider>
  );
};

export const useChildComponentPropsContext = () => {
  return useContext(ChildComponentPropsContext);
};
```

**实现要点**：

- 内置 Ant Design Form 实例
- 使用 useMemo 缓存 contextValue
- 简单直接，职责单一

### 4. HandleActionsProvider 实现

位置：`packages/materials/src/components/pisellDataSourceContainer/provider/HandleActions.tsx`

```
export const HandleActionsProvider = (props: DataSourceContainerProps) => {
  const { dataSource, children, autoRun, pagination, extraParams } = props;
  const { form } = useChildComponentPropsContext();
  const componentId = useComponentId(props);
  const { registerValueVariable } = useVariables();
  const { dataSourceKey } = useDataSourceKey();

  // 使用 useHandleAction Hook 封装操作
  const handleAction = useHandleAction(
    { list: extraParams?.list },
    pagination
  );

  // 注册变量
  useEffect(() => {
    registerValueVariable?.(componentId, {
      currentDataSource: dataSource,
    });
  }, [dataSource]);

  // 自动执行查询
  useEffect(() => {
    if (isBoolean(autoRun?.list) && autoRun?.list) {
      const formValues = form.getFieldsValue() || {};
      if (dataSourceKey) {
        handleAction.list.onSearch({ ...formValues });
      } else {
        handleAction.list?.mutate([]);
      }
    }
  }, [dataSourceKey, autoRun?.list]);

  return (
    <HandleActionContext.Provider value={handleAction}>
      {children}
    </HandleActionContext.Provider>
  );
};
```

**实现要点**：

- 依赖 ChildComponentPropsProvider 获取 form 实例
- 自动执行逻辑在 useEffect 中处理
- 将 handleAction 提供给子组件

### 5. useHandleAction Hook 实现

位置：`packages/materials/src/components/pisellDataSourceContainer/hooks/useHandleAction.ts`

#### 分页状态管理

```
const useHandleAction = (
  initialParams: Record<string, any> = {},
  options: DataSourceContainerProps['pagination']
) => {
  const { list, get, update, create, destroy, getCurrentRecord } = useDataSource();

  // 分页配置规范化
  const normalizedOptions =
    options === true
      ? { pageNumber: 1, pageSize: 10 }
      : typeof options === 'object'
      ? options
      : {};

  const { pageNumber, pageSize } = normalizedOptions;
  const enablePagination = pageNumber !== undefined && pageSize !== undefined;

  // 缓存初始参数（用于 reset）
  const initialParamsRef = useRef(initialParams);

  // 分页状态
  const [pagination, setPagination] = useState({
    pageNumber: pageNumber ?? 1,
    pageSize: pageSize ?? 10,
  });

  // ... onSearch、onReset、onPageChange 实现
};
```

**实现要点**：

- 分页配置规范化处理（支持 boolean 和对象两种形式）
- 使用 useRef 缓存初始参数，避免闭包问题
- 使用 useState 管理分页状态

#### onSearch 实现

```
const onSearch = useCallback(
  (newParams?: Record<string, any>) => {
    let searchParams: Record<string, any> = {
      ...(params?.[0] || {}),
      ...initialParamsRef.current?.list || {},
      ...newParams,
    };

    if (enablePagination) {
      searchParams = {
        ...searchParams,
        pageNumber: 1, // 查询时始终重置到第一页
        pageSize: pagination.pageSize,
      };

      if (pagination.pageNumber !== 1) {
        setPagination((prev) => ({ ...prev, pageNumber: 1 }));
      }
    }

    run(searchParams);
  },
  [enablePagination, pagination.pageNumber, pagination.pageSize, params, run]
);
```

**实现要点**：

- 参数合并：newParams > initialParams > params
- 自动重置分页：查询时回到第一页
- 使用 useCallback 优化性能
- 同步更新分页状态

#### onReset 实现

```
const onReset = useCallback(() => {
  let resetParams: Record<string, any> = {
    ...initialParamsRef.current?.list || {},
  };

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

  run(resetParams);
}, [run, enablePagination, pageNumber, pageSize]);
```

**实现要点**：

- 完全重置到初始状态
- 清空所有查询条件
- 恢复初始分页配置

#### onPageChange 实现

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

    if (pageNumber < 1 || pageSize < 1) return;

    setPagination({ pageNumber, pageSize });

    const lastParams = params?.[0] || { ...initialParamsRef.current?.list || {} };
    run({
      ...lastParams,
      pageNumber,
      pageSize,
    });
  },
  [enablePagination, params, run]
);
```

**实现要点**：

- 保留上一次的查询参数
- 更新分页参数
- 边界检查（pageNumber 和 pageSize 必须大于 0）

### 6. useContainerContext Hook 实现

位置：`packages/materials/src/components/pisellDataSourceContainer/hooks/useContainerContext.ts`

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

  // 表单值变化回调
  const onValuesChange = useCallback(
    (changedValues: Record<string, any>, _allValues: Record<string, any>) => {
      console.log('changedValues', changedValues, _allValues);
      try {
        handleActionsValue.list.onSearch(changedValues);
      } catch (error) {
        console.error('[useContainerContext] onValuesChange 执行失败:', error);
      }
    },
    [handleActionsValue.list]
  );

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

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

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

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

**实现要点**：

- 聚合所有 Provider 的能力
- 按需注入子组件属性（通过 displayName）
- onValuesChange 自动触发 onSearch
- 使用 useMemo 优化性能

### 7. withContainer HOC 实现

位置：`packages/materials/src/components/pisellDataSourceContainer/utils/withContainer.tsx`

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

      // 获取容器上下文（自动注入子组件属性）
      const containerContext = useContainerContext(displayName);

      // 内部 ref
      const innerRef = useRef<R>(null);

      // 转发 ref 给外部
      useImperativeHandle(
        forwardedRef,
        () => {
          return innerRef.current as R;
        },
        []
      );

      // 自动注册 ref 到 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(() => {
        return {
          ...containerContext,
          ...otherProps,
        } as P & UseContainerContextReturnType;
      }, [otherProps, containerContext]);

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

  return WithDataContainerComponent;
}
```

**实现要点**：

- 自动调用 useContainerContext，注入上下文
- 支持 refId 属性，自动注册和注销 ref
- 使用 forwardRef 支持 ref 转发
- 使用 useImperativeHandle 暴露组件方法
- props 合并：containerContext + otherProps

## 开发新子组件的步骤

### 1. 使用 Hook 方式（推荐）

```
// 创建组件文件
// packages/materials/src/components/pisellDataSourceContainer/components/MyComponent/index.tsx

import React from 'react';
import useContainerContext from '../../hooks/useContainerContext';

const MyComponent: React.FC = () => {
  // 获取容器上下文
  const { serverActions, refManager, form } = useContainerContext('myComponent');

  const handleClick = () => {
    serverActions.list.onSearch({ keyword: 'test' });
  };

  return (
    <div>
      <button onClick={handleClick}>查询</button>
      {serverActions.list.data.list.map(item => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  );
};

export default MyComponent;
```

### 2. 使用 HOC 方式

```
// 定义组件
import React from 'react';
import { UseContainerContextReturnType } from '../../type';
import withContainer from '../../utils/withContainer';

interface MyComponentProps {
  title?: string;
}

const MyComponentInner: React.FC<MyComponentProps & UseContainerContextReturnType> = ({
  title,
  serverActions,
  refManager,
  form,
}) => {
  return (
    <div>
      <h2>{title}</h2>
      {/* 业务逻辑 */}
    </div>
  );
};

// 使用 HOC 包装
const MyComponent = withContainer(MyComponentInner, 'myComponent');

export default MyComponent;
```

### 3. 添加子组件属性类型

```
// 在 type.ts 中添加类型定义
export interface ChildComponentPropsType {
  // ... 其他子组件配置
  myComponent?: {
    title?: string;
    onCustomEvent?: () => void;
  };
}
```

### 4. 使用组件

```
<PisellDataSourceContainer
  dataSource="users"
  childComponentProps={{
    myComponent: {
      title: '我的组件',
      onCustomEvent: () => console.log('custom event')
    }
  }}
>
  <MyComponent refId="myComp" />
</PisellDataSourceContainer>
```

## 技术要点

### 1. 性能优化

#### useMemo 使用

```
// 缓存计算结果
const mergedProps = useMemo(() => {
  return {
    ...contextValue,
    ...otherProps,
  };
}, [contextValue, otherProps]);
```

#### useCallback 使用

```
// 缓存回调函数
const onSearch = useCallback(
  (params) => {
    // 查询逻辑
  },
  [deps]
);
```

#### Context 分层

- 分层 Provider 避免不必要的重新渲染
- 每个 Provider 只提供相关的能力

### 2. TypeScript 类型设计

#### 泛型使用

```
// Ref 管理支持泛型
const getRef = <T = any,>(id: string): T | undefined => {
  return refMapRef.current.get(id);
};

// SharedState 支持泛型
const setSharedState = <T = any,>(key: string, value: T) => {
  sharedStateRef.current.set(key, value);
};
```

#### 类型扩展

```
// 扩展 HandleActionContextType
export interface HandleActionContextType extends Omit<DataSourceContextType, 'list'> {
  list: Result<any, any> & {
    onSearch: (newParams?: Record<string, any>) => void;
    onReset: () => void;
    onPageChange: (pageNumber: number, pageSize: number) => void;
    pagination?: { pageNumber: number; pageSize: number };
  };
}
```

### 3. 错误处理

```
// 边界检查
if (!id) {
  console.warn('[RefManager] registerRef: id 不能为空');
  return;
}

// try-catch 保护
try {
  callback(value);
} catch (error) {
  console.error(`[RefManager] 订阅回调执行失败:`, error);
}
```

### 4. 生命周期管理

```
// 自动注册和注销
useEffect(() => {
  if (refId && innerRef.current) {
    containerContext.refManager.registerRef(refId, innerRef.current);
  }

  return () => {
    if (refId) {
      containerContext.refManager.unregisterRef(refId);
    }
  };
}, [refId]);

// 订阅返回取消订阅函数
useEffect(() => {
  const unsubscribe = refManager.subscribeSharedState('key', callback);
  return unsubscribe;
}, []);
```

## 调试技巧

### 1. 查看 Context 值

```
const context = useContainerContext();
console.log('Container Context:', context);
```

### 2. 监听状态变化

```
useEffect(() => {
  const unsubscribe = refManager.subscribeSharedState('key', (value) => {
    console.log('State changed:', value);
  });
  return unsubscribe;
}, []);
```

### 3. 查看请求参数

```
const { serverActions } = useContainerContext();
console.log('Request params:', serverActions.list.params);
```

### 4. React DevTools

使用 React DevTools 查看：

- Context 值
- 组件 Props
- Hooks 状态

## 常见问题

### 1. 为什么我的组件获取不到 context？

**原因**：组件不在 PisellDataSourceContainer 内部

**解决**：

```
// 错误
<MyComponent />
<PisellDataSourceContainer>...</PisellDataSourceContainer>

// 正确
<PisellDataSourceContainer>
  <MyComponent />
</PisellDataSourceContainer>
```

### 2. 为什么 onSearch 没有触发？

**原因**：可能是参数格式不正确或数据源配置有问题

**排查**：

```
const { serverActions } = useContainerContext();
console.log('dataSourceKey:', serverActions.dataSource);
console.log('params:', serverActions.list.params);
```

### 3. 为什么 SharedState 没有更新？

**原因**：没有正确订阅或订阅时机不对

**解决**：

```
// 确保在 useEffect 中订阅
useEffect(() => {
  return refManager.subscribeSharedState('key', (value) => {
    setState(value);
  });
}, [refManager]);
```

### 4. 为什么分页切换后查询条件丢失？

**原因**：onPageChange 应该保留查询参数

**检查**：确保 onPageChange 实现正确保留了上一次的查询参数

## 最佳实践

### 1. 组件职责单一

```
// Good: 职责单一
const FilterComponent = () => {
  const { form, serverActions } = useContainerContext();
  // 只处理筛选逻辑
};

const TableComponent = () => {
  const { serverActions } = useContainerContext();
  // 只处理表格展示
};

// Bad: 一个组件做太多事情
const HugeComponent = () => {
  // 筛选、表格、分页、操作全在一起
};
```

### 2. 合理使用 displayName

```
// Good: 指定 displayName 获取特定配置
const MyTable = () => {
  const { columns, serverActions } = useContainerContext('tableComponent');
  // columns 会被自动注入
};

// Bad: 不使用 displayName，手动从 props 传递
const MyTable = ({ columns }) => {
  const { serverActions } = useContainerContext();
  // 需要手动传递 columns
};
```

### 3. 状态管理选择

```
// 容器内状态：使用 SharedState
refManager.setSharedState('selectedIds', ids);

// 组件内状态：使用 useState
const [localState, setLocalState] = useState();

// 全局状态：使用 Redux/Zustand
```

### 4. 错误处理

```
const handleDelete = async () => {
  try {
    await serverActions.destroy.run({ id });
    message.success('删除成功');
    serverActions.list.onSearch(); // 刷新列表
  } catch (error) {
    message.error('删除失败');
    console.error(error);
  }
};
```
