Angular中如何编写可复用函数处理不同组件的差异化数据?
Angular 组件复用方案:处理相似结构的列表类组件
这是个很典型的Angular代码复用场景,针对你这种大量结构相似、仅细节差异的组件,我推荐几个实用的方案,按复用程度从高到低排序:
方案一:抽象基类(最适合你的场景)
把所有组件的通用逻辑抽成抽象基类,让具体组件继承并实现差异化部分。这种方式能最大化复用代码,同时保留组件各自的独特性。
步骤1:定义通用类型与抽象基类
先给通用部分定义类型,再写抽象基类封装重复逻辑:
import { OnInit } from '@angular/core'; // 通用分页类型 export interface PaginationState { totalData: number | null; currentPage: number | null; } // 通用数据容器类型(泛型支持不同数据结构) export interface DataContainer<T> { items: T[] | null; isEmpty: boolean | null; getData: () => void; } // 抽象基类,泛型T对应数据项类型,F对应过滤参数类型 export abstract class BaseListComponent<T, F> implements OnInit { data: DataContainer<T>; pagination: PaginationState & { goNext: () => void; goLast: () => void; }; filter: F; constructor() { // 封装通用分页逻辑 this.pagination = { totalData: null, currentPage: 1, // 默认第一页 goNext: () => { if (this.pagination.currentPage !== null) { this.pagination.currentPage += 1; this.data.getData(); } }, goLast: () => { if (this.pagination.totalData !== null) { this.pagination.currentPage = this.pagination.totalData; this.data.getData(); } } }; } ngOnInit(): void { // 通用初始化逻辑 this.initFilter(); this.data.getData(); } // 抽象方法:子类必须实现过滤参数的初始化 protected abstract initFilter(): void; }
步骤2:具体组件继承基类
以PlanningComponent为例,只需要实现差异化的data和filter:
// 定义当前组件的专属类型 interface PlanningItem { /* 规划数据的字段 */ } interface PlanningFilter { filterA: string | null; filterB: number | null; filterC: string | null; isEnabled: boolean | null; isToggled: boolean | null; onReset: () => void; } export class PlanningComponent extends BaseListComponent<PlanningItem, PlanningFilter> { constructor() { super(); // 初始化专属数据逻辑 this.data = { items: null, isEmpty: null, getData: () => { // 这里写获取规划数据的具体逻辑,同时更新pagination.totalData // 示例: // this.planningService.fetchItems(this.filter, this.pagination.currentPage) // .subscribe(res => { // this.data.items = res.list; // this.pagination.totalData = res.totalPages; // this.data.isEmpty = res.list.length === 0; // }); } }; } protected initFilter(): void { // 初始化专属过滤参数与重置逻辑 this.filter = { filterA: null, filterB: null, filterC: null, isEnabled: null, isToggled: null, onReset: () => { this.filter.filterA = this.filter.filterB = this.filter.filterC = null; this.data.getData(); } }; } }
HarvestComponent的实现逻辑完全一致,只需要替换对应的HarvestItem和HarvestFilter类型即可。
优点:代码复用率极高,所有通用逻辑只写一次;类型安全,通过泛型保证组件数据结构的正确性。
缺点:继承带来一定耦合性,若基类逻辑变动,所有子类都会受影响。
方案二:服务封装通用逻辑
如果不想用继承,可以把分页、过滤这类通用逻辑封装成服务,组件按需调用服务生成对应的逻辑对象。
示例:ListService封装通用方法
import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class ListService { // 生成分页逻辑 createPagination(loadData: () => void) { const state = { totalData: null as number | null, currentPage: 1 as number | null }; return { ...state, goNext: () => { if (state.currentPage !== null) { state.currentPage += 1; loadData(); } }, goLast: () => { if (state.totalData !== null) { state.currentPage = state.totalData; loadData(); } } }; } // 生成过滤重置逻辑 createFilterReset<T>(filter: T, resetFields: (keyof T)[], loadData: () => void) { return () => { resetFields.forEach(field => filter[field] = null); loadData(); }; } }
组件中使用服务
export class PlanningComponent implements OnInit { data = { items: null as PlanningItem[] | null, isEmpty: null as boolean | null, getData: () => { /* 具体数据逻辑 */ } }; pagination: ReturnType<ListService['createPagination']>; filter = { filterA: null as string | null, filterB: null as number | null, filterC: null as string | null, isEnabled: null as boolean | null, isToggled: null as boolean | null, onReset: () => {} }; constructor(private listService: ListService) {} ngOnInit(): void { // 调用服务生成分页逻辑 this.pagination = this.listService.createPagination(() => this.data.getData()); // 调用服务生成过滤重置逻辑 this.filter.onReset = this.listService.createFilterReset( this.filter, ['filterA', 'filterB', 'filterC'], () => this.data.getData() ); } }
优点:耦合性低,组件可以灵活选择是否使用服务的逻辑;适合不需要完全统一结构的场景。
缺点:每个组件仍需要初始化基础的data和filter对象,复用程度略低于抽象基类。
方案三:独立UI组件(复用UI+逻辑)
如果你的分页、过滤的UI也高度相似,可以把它们拆成独立的子组件,通过@Input/@Output与父组件交互。
示例:PaginationComponent
import { Component, Input, Output, EventEmitter } from '@angular/core'; @Component({ selector: 'app-pagination', template: ` <div class="pagination"> <button (click)="goNext.emit()" [disabled]="currentPage >= totalData"> Next Page </button> <button (click)="goLast.emit()" [disabled]="currentPage === totalData"> Last Page </button> <span>Page {{currentPage}} / {{totalData || 0}}</span> </div> ` }) export class PaginationComponent { @Input() totalData: number | null = null; @Input() currentPage: number | null = null; @Output() goNext = new EventEmitter<void>(); @Output() goLast = new EventEmitter<void>(); }
父组件中使用子组件
<!-- PlanningComponent模板 --> <app-pagination [totalData]="pagination.totalData" [currentPage]="pagination.currentPage" (goNext)="handleGoNext()" (goLast)="handleGoLast()"> </app-pagination>
// PlanningComponent逻辑 handleGoNext() { this.pagination.currentPage += 1; this.data.getData(); } handleGoLast() { this.pagination.currentPage = this.pagination.totalData; this.data.getData(); }
过滤组件的实现逻辑类似,把过滤表单封装成子组件,通过@Output发出过滤参数变化的事件。
优点:同时复用UI和逻辑,适合需要统一页面样式的场景。
缺点:需要额外维护子组件,适合UI相似的情况,若UI差异较大则不适用。
选择建议
- 如果你的组件逻辑、结构高度一致,优先选抽象基类,能最大程度减少重复代码;
- 如果组件之间有一定差异,不想用继承,选服务封装更灵活;
- 如果UI也高度相似,再结合独立UI组件进一步复用。
内容的提问来源于stack exchange,提问作者Raka Pratama
相关产品推荐
相关产品推荐

