SICK RFID TCP客户端:解决工业现场假读卡与粘包问题
2026/10/26 2:24:14
Angular 应用的综合性能优化指南。包含消除性能瓶颈、优化包和改进渲染的优先规则。
在以下情况下参考这些指南:
| Priority | Category | Impact | Focus |
|---|---|---|---|
| 1 | Change Detection | CRITICAL | Signals, OnPush, Zoneless |
| 2 | Async Waterfalls | CRITICAL | RxJS patterns, SSR preloading |
| 3 | Bundle Optimization | CRITICAL | Lazy loading, tree shaking |
| 4 | Rendering Performance | HIGH | @defer, trackBy, virtualization |
| 5 | Server-Side Rendering | HIGH | Hydration, prerendering |
| 6 | Template Optimization | MEDIUM | Control flow, pipes |
| 7 | State Management | MEDIUM | Signal patterns, selectors |
| 8 | Memory Management | LOW-MEDIUM | Cleanup, subscriptions |
// CORRECT - OnPush with Signals@Component({changeDetection:ChangeDetectionStrategy.OnPush,template:`<div>{{ count() }}</div>`,})exportclassCounterComponent{count=signal(0);}// WRONG - Default change detection@Component({template:`<div>{{ count }}</div>`,// Checked every cycle})exportclassCounterComponent{count=0;}// CORRECT - Signals trigger precise updates@Component({template:`<h1>{{ title() }}</h1> <p>Count: {{ count() }}</p>`,})exportclassDashboardComponent{title=signal("Dashboard");count=signal(0);}// WRONG - Mutable properties require zone.js checks@Component({template:`<h1>{{ title }}</h1> <p>Count: {{ count }}</p>`,})exportclassDashboardComponent{title="Dashboard";count=0;}// main.ts - Zoneless Angular (v20+)bootstrapApplication(AppComponent,{providers:[provideZonelessChangeDetection()],});好处:
// WRONG - Nested subscriptions create waterfallsthis.route.params.subscribe((params)=>{// 1. Wait for paramsthis.userService.getUser(params.id).subscribe((user)=>{// 2. Wait for userthis.postsService.getPosts(user.id).subscribe((posts)=>{// 3. Wait for posts});});});// CORRECT - Parallel execution with forkJoinforkJoin({user:this.userService.getUser(id),posts:this.postsService.getPosts(id),}).subscribe((data)=>{// Fetched in parallel});// CORRECT - Flatten dependent calls with switchMapthis.route.params.pipe(map((p)=>p.id),switchMap((id)=>this.userService.getUser(id)),).subscribe();// CORRECT - Use resolvers or blocking hydration for critical dataexportconstroute:Route={path:"profile/:id",resolve:{data:profileResolver},// Fetched on server before navigationcomponent:ProfileComponent,};// WRONG - Component fetches data on initclassProfileComponentimplementsOnInit{ngOnInit(){// Starts ONLY after JS loads and component rendersthis.http.get("/api/profile").subscribe();}}// CORRECT - Lazy load feature routesexportconstroutes:Routes=[{path:"admin",loadChildren:()=>import("./admin/admin.routes").then((m)=>m.ADMIN_ROUTES),},{path:"dashboard",loadComponent:()=>import("./dashboard/dashboard.component").then((m)=>m.DashboardComponent,),},];// WRONG - Eager loading everythingimport{AdminModule}from"./admin/admin.module";exportconstroutes:Routes=[{path:"admin",component:AdminComponent},// In main bundle];<!-- CORRECT - Heavy component loads on demand -->@defer (on viewport) {<app-analytics-chart[data]="data()"/>} @placeholder {<divclass="chart-skeleton"></div>}<!-- WRONG - Heavy component in initial bundle --><app-analytics-chart[data]="data()"/>// WRONG - Imports entire barrel, breaks tree-shakingimport{Button,Modal,Table}from"@shared/components";// CORRECT - Direct importsimport{Button}from"@shared/components/button/button.component";import{Modal}from"@shared/components/modal/modal.component";// CORRECT - Load heavy library on demandasyncloadChart(){const{Chart}=awaitimport('chart.js');this.chart=newChart(this.canvas,config);}// WRONG - Bundle Chart.js in main chunkimport{Chart}from'chart.js';<!-- CORRECT - Efficient DOM updates -->@for (item of items(); track item.id) {<app-item-card[item]="item"/>}<!-- WRONG - Entire list re-renders on any change -->@for (item of items(); track $index) {<app-item-card[item]="item"/>}import{CdkVirtualScrollViewport,CdkFixedSizeVirtualScroll}from'@angular/cdk/scrolling';@Component({imports:[CdkVirtualScrollViewport,CdkFixedSizeVirtualScroll],template:`<cdk-virtual-scroll-viewport itemSize="50" class="viewport"> <div *cdkVirtualFor="let item of items" class="item"> {{ item.name }} </div> </cdk-virtual-scroll-viewport>`})// CORRECT - Pure pipe, memoized@Pipe({name:'filterActive',standalone:true,pure:true})exportclassFilterActivePipeimplementsPipeTransform{transform(items:Item[]):Item[]{returnitems.filter(i=>i.active);}}// Template@for(itemofitems()|filterActive;track item.id){...}// WRONG - Method called every change detection@for(itemofgetActiveItems();track item.id){...}// CORRECT - Computed, cached until dependencies changeexportclassProductStore{products=signal<Product[]>([]);filter=signal('');filteredProducts=computed(()=>{constf=this.filter().toLowerCase();returnthis.products().filter(p=>p.name.toLowerCase().includes(f));});}// WRONG - Recalculates every accessgetfilteredProducts(){returnthis.products.filter(p=>p.name.toLowerCase().includes(this.filter));}// app.config.tsimport{provideClientHydration,withIncrementalHydration,}from"@angular/platform-browser";exportconstappConfig:ApplicationConfig={providers:[provideClientHydration(withIncrementalHydration(),withEventReplay()),],};<!-- Critical above-the-fold content --><app-header/><app-hero/><!-- Below-fold deferred with hydration triggers -->@defer (hydrate on viewport) {<app-product-grid/>} @defer (hydrate on interaction) {<app-chat-widget/>}@Injectable({providedIn:"root"})exportclassDataService{privatehttp=inject(HttpClient);privatetransferState=inject(TransferState);privateplatformId=inject(PLATFORM_ID);getData(key:string):Observable<Data>{conststateKey=makeStateKey<Data>(key);if(isPlatformBrowser(this.platformId)){constcached=this.transferState.get(stateKey,null);if(cached){this.transferState.remove(stateKey);returnof(cached);}}returnthis.http.get<Data>(`/api/${key}`).pipe(tap((data)=>{if(isPlatformServer(this.platformId)){this.transferState.set(stateKey,data);}}),);}}<!-- CORRECT - New control flow (faster, smaller bundle) -->@if (user()) {<span>{{ user()!.name }}</span>} @else {<span>Guest</span>} @for (item of items(); track item.id) {<app-item[item]="item"/>} @empty {<p>No items</p>}<!-- WRONG - Legacy structural directives --><span*ngIf="user; else guest">{{ user.name }}</span><ng-template#guest><span>Guest</span></ng-template>// CORRECT - Precompute in componentclassComponent{items=signal<Item[]>([]);sortedItems=computed(()=>[...this.items()].sort((a,b)=>a.name.localeCompare(b.name)));}// Template@for(itemofsortedItems();track item.id){...}// WRONG - Sorting in template every render@for(itemofitems()|sort:'name';track item.id){...}// CORRECT - Selective subscription@Component({template:`<span>{{ userName() }}</span>`,})classHeaderComponent{privatestore=inject(Store);// Only re-renders when userName changesuserName=this.store.selectSignal(selectUserName);}// WRONG - Subscribing to entire state@Component({template:`<span>{{ state().user.name }}</span>`,})classHeaderComponent{privatestore=inject(Store);// Re-renders on ANY state changestate=toSignal(this.store);}// CORRECT - Feature-scoped store@Injectable()// NOT providedIn: 'root'exportclassProductStore{...}@Component({providers:[ProductStore],// Scoped to component tree})exportclassProductPageComponent{store=inject(ProductStore);}// WRONG - Everything in global store@Injectable({providedIn:'root'})exportclassGlobalStore{// Contains ALL app state - hard to tree-shake}import{takeUntilDestroyed}from'@angular/core/rxjs-interop';@Component({...})exportclassDataComponent{privatedestroyRef=inject(DestroyRef);constructor(){this.data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(data=>this.process(data));}}// WRONG - Manual subscription managementexportclassDataComponentimplementsOnDestroy{privatesubscription!:Subscription;ngOnInit(){this.subscription=this.data$.subscribe(...);}ngOnDestroy(){this.subscription.unsubscribe();// Easy to forget}}// CORRECT - No subscription needed@Component({template:`<div>{{ data().name }}</div>`,})exportclassComponent{data=toSignal(this.service.data$,{initialValue:null});}// WRONG - Manual subscription@Component({template:`<div>{{ data?.name }}</div>`,})exportclassComponentimplementsOnInit,OnDestroy{data:Data|null=null;privatesub!:Subscription;ngOnInit(){this.sub=this.service.data$.subscribe((d)=>(this.data=d));}ngOnDestroy(){this.sub.unsubscribe();}}changeDetection: ChangeDetectionStrategy.OnPush独立: truesignal()、input()、output())inject()@for与track表达式@defer (在...时水合)