組件封裝 - 基礎組件
動(dòng)態(tài)表格組件 (DynamicTable)
1. 組件功能特性
- 列自定義顯示/隱藏
- 固定列支持
- 行選擇和操作
- 排序和篩選
- 分頁(yè)集成
- 自定義渲染
2. 組件使用示例
// 基本使用
<DynamicTable
id="ckgd"
myKey="entryId"
columns={columns}
dataSource={dataSource}
showSelectionCheckbox={true}
onTableSelect={this.handleTableSelect}
loading={loading}
/>
// 帶操作的表格
<DynamicTable
id="goods"
myKey="id"
columns={goodsColumns}
dataSource={goodsData}
actions={[
{ label: '查看詳情', onClick: this.handleViewDetail },
{ label: '編輯', onClick: this.handleEdit }
]}
onDoubleClick={this.handleRowDoubleClick}
/>
3. 列配置定義
const columns = [
{
title: '報關(guān)單號',
dataIndex: 'entryId',
key: 'entryId',
width: 160,
sorter: (a, b) => a.entryId - b.entryId,
fixed: 'left'
},
{
title: '出口日期',
dataIndex: 'iEDate',
key: 'iEDate',
width: 90,
sorter: (a, b) => new Date(a.iEDate) - new Date(b.iEDate),
render: (text) => moment(text).format('YYYY-MM-DD')
},
{
title: '申報狀態(tài)',
dataIndex: 'sbztDm',
key: 'sbztDm',
render: (text) => {
switch (text) {
case 'Y': return '已申報';
case 'N': return '未申報';
case 'B': return '部分申報';
default: return text;
}
}
}
];
4. 組件核心實(shí)現
class DynamicTable extends Component {
constructor(props) {
super(props);
this.state = {
columns: this.initializeColumns(props.columns, props.fixedColumns),
dataSource: props.dataSource || [],
selectedRowKeys: props.selectedRowKeys || [],
loading: props.loading || false
};
}
// 初始化列配置
initializeColumns = (columns, fixedColumns, dataSource) => {
return columns
.filter(col => !col.unShow) // 過(guò)濾隱藏列
.map(col => {
let width = this.calculateColumnWidth(col, dataSource);
width = width < 350 ? width : 350;
if (fixedColumns && fixedColumns.includes(col.key)) {
return { ...col, ellipsis: true, fixed: 'left', width };
}
return { ...col, ellipsis: true, width };
});
};
// 計算列寬度
calculateColumnWidth = (col, dataSource) => {
const contentWidth = Math.max(
col.title.length * 10, // 標題寬度
...dataSource.map(item =>
String(item[col.dataIndex] || '').length * 8 // 內容寬度
)
);
return Math.max(contentWidth + 20, 80); // 最小寬度80px
};
render() {
const { columns, dataSource, loading } = this.state;
const { id, myKey, showSelectionCheckbox } = this.props;
return (
<Table
id={id}
rowKey={record => record[myKey]}
columns={columns}
dataSource={dataSource}
loading={loading}
rowSelection={showSelectionCheckbox ? {
selectedRowKeys: this.state.selectedRowKeys,
onChange: this.handleSelectionChange
} : null}
scroll={{ x: 1500, y: 400 }}
pagination={false}
/>
);
}
}
動(dòng)態(tài)表單組件 (DynamicForm)
1. 表單字段類(lèi)型支持
- Input - 文本輸入
- Select - 下拉選擇
- DatePicker - 日期選擇
- Checkbox - 復選框
- Radio - 單選框
- Button - 操作按鈕
2. 表單配置示例
const formConfig = {
id: 'searchForm',
formItems: [
{
type: 'input',
field: 'entryId',
label: '報關(guān)單號',
placeholder: '請輸入報關(guān)單號'
},
{
type: 'select',
field: 'cusDecStatus',
label: '結關(guān)狀態(tài)',
options: [
{ value: '', label: '全部' },
{ value: '10', label: '已結關(guān)' },
{ value: 'wjg', label: '未結關(guān)' }
],
fieldProps: {
initialValue: ''
}
},
{
type: 'DatePicker',
field: 'iEDate',
label: '出口日期',
nextType: 'RangePicker' // 支持范圍選擇
},
{
type: 'button',
action: 'submit',
label: '查詢(xún)',
btnType: 'primary'
}
],
layout: 'inline',
onFormSubmit: this.handleSearchSubmit
};
3. 組件使用
<DynamicForm {...formConfig} />
布局組件系統
1. 雙欄布局 (twinlayout)
// 主要用于內容展示頁(yè)面
<Route name="txrp" path="/txrp" component={twinlayout}>
<Route name="出口貨物明細采集" path="ckhwmxcj" component={ckhwmxcj} />
<Route name="貨物資料采集" path="hwzlcj" component={hwzlcj} />
</Route>
2. 橫向布局 (hglayout)
// 用于橫向導航的頁(yè)面
<Route name="ckfp" path="ckfp" component={hglayout}>
<Route name="出口關(guān)單" path="ckgd" component={ckgd} />
<Route name="退稅率查詢(xún)" path="tslcx" component={tslcx} />
</Route>
3. 出口關(guān)單布局 (cklayout)
// 專(zhuān)門(mén)的出口關(guān)單頁(yè)面布局
<Route path="/" component={cklayout}>
<IndexRoute component={Home} />
<Route path="ckgd" component={ckgd} />
</Route>
基礎組件封裝
1. 動(dòng)態(tài)分頁(yè)組件 (DynamicPagination)
class DynamicPagination extends Component {
render() {
const { totalItems, currentPage, initialPageSize, onPageChange } = this.props;
return (
<Pagination
current={currentPage}
pageSize={initialPageSize}
total={totalItems}
showSizeChanger
showQuickJumper
showTotal={(total, range) =>
`第 ${range[0]}-${range[1]} 條,共 ${total} 條`
}
onChange={onPageChange}
onShowSizeChange={onPageChange}
/>
);
}
}
2. 動(dòng)態(tài)卡片組件 (DynamicCard)
class DynamicCard extends Component {
render() {
const { title, extra, children, tabList, activeKey, onTabChange } = this.props;
return (
<Card
title={title}
extra={extra}
tabList={tabList}
activeTabKey={activeKey}
onTabChange={onTabChange}
>
{children}
</Card>
);
}
}
組件通信模式
1. Props傳遞
// 父組件傳遞數據和回調
<ChildComponent
data={this.state.data}
onUpdate={this.handleChildUpdate}
/>
// 子組件接收和使用
class ChildComponent extends Component {
handleClick = () => {
this.props.onUpdate(newData);
};
}
2. Context傳遞
// 創(chuàng )建Context
const TableContext = React.createContext();
// 提供者組件
class TableProvider extends Component {
state = { selectedRows: [] };
render() {
return (
<TableContext.Provider value={{
selectedRows: this.state.selectedRows,
setSelectedRows: this.setSelectedRows
}}>
{this.props.children}
</TableContext.Provider>
);
}
}
// 消費者組件
const TableConsumer = () => (
<TableContext.Consumer>
{({ selectedRows, setSelectedRows }) => (
// 使用上下文數據
)}
</TableContext.Consumer>
);
組件性能優(yōu)化
1. 避免不必要的重新渲染
class OptimizedComponent extends React.PureComponent {
// 自動(dòng)實(shí)現shouldComponentUpdate
// 進(jìn)行淺比較props和state
}
// 或手動(dòng)實(shí)現
class OptimizedComponent extends Component {
shouldComponentUpdate(nextProps, nextState) {
// 自定義比較邏輯
return nextProps.data !== this.props.data;
}
}
2. 使用React.memo
const MemoizedComponent = React.memo(function MyComponent(props) {
// 組件實(shí)現
}, (prevProps, nextProps) => {
// 自定義比較函數
return prevProps.data === nextProps.data;
});
3. 代碼分割和懶加載
const LazyComponent = React.lazy(() => import('./LazyComponent'));
const MyComponent = () => (
<Suspense fallback={<div>加載中...</div>}>
<LazyComponent />
</Suspense>
);
組件測試
1. 單元測試示例
import { render, screen, fireEvent } from '@testing-library/react';
import DynamicTable from './DynamicTable';
test('renders table with data', () => {
const mockData = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
];
render(<DynamicTable dataSource={mockData} />);
expect(screen.getByText('Item 1')).toBeInTheDocument();
expect(screen.getByText('Item 2')).toBeInTheDocument();
});
test('handles row selection', () => {
const mockData = [{ id: 1, name: 'Item 1' }];
const mockOnSelect = jest.fn();
render(<DynamicTable dataSource={mockData} onTableSelect={mockOnSelect} />);
fireEvent.click(screen.getByText('Item 1'));
expect(mockOnSelect).toHaveBeenCalledWith([1]);
});