基于NgRx实现多组件滚动同步的方案咨询
用NgRx实现两个组件滚动同步的完整方案
1. 修正Reducer逻辑
你当前的Reducer没有更新状态,需要修改为接收动作传入的data并返回新状态(保持NgRx状态不可变性):
export const initialState: Readonly<ListStateModel> = {displayedElemList: [], scrollY: 0}; export const scrollReducer = createReducer( initialState, on(getScrollAction, (_state, { data }) => { return { ..._state, scrollY: data.scrollY, displayedElemList: data.displayedElemList }; }) );
2. 创建Selector
通过Selector封装状态查询逻辑,让组件可以精准订阅所需数据:
import { createFeatureSelector, createSelector } from '@ngrx/store'; // 获取整个列表状态切片 export const selectListState = createFeatureSelector<ListStateModel>('list'); // 派生滚动位置的Selector export const selectScrollY = createSelector( selectListState, (state) => state.scrollY ); // 派生显示元素列表的Selector export const selectDisplayedElemList = createSelector( selectListState, (state) => state.displayedElemList );
3. 滚动发起组件实现
监听滚动事件,将滚动位置和列表数据dispatch到Store:
模板(scroll-source.component.html)
<div class="scroll-container" (scroll)="onScroll($event)"> <div *ngFor="let item of displayedList">{{ item }}</div> </div>
组件类(scroll-source.component.ts)
import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import { getScrollAction } from './path-to-actions'; import { ListStateModel } from './path-to-model'; @Component({ selector: 'app-scroll-source', templateUrl: './scroll-source.component.html', styleUrls: ['./scroll-source.component.css'] }) export class ScrollSourceComponent { displayedList: any[] = []; // 替换为你的实际列表数据 constructor(private store: Store) {} onScroll(event: Event) { const target = event.target as HTMLElement; const scrollData: ListStateModel = { scrollY: target.scrollTop, displayedElemList: this.displayedList }; this.store.dispatch(getScrollAction({ data: scrollData })); } }
4. 滚动同步组件实现
订阅Store中的滚动位置,同步自身容器的滚动状态:
模板(scroll-target.component.html)
<div class="scroll-container" #scrollContainer> <div *ngFor="let item of displayedList$ | async">{{ item }}</div> </div>
组件类(scroll-target.component.ts)
import { Component, ElementRef, ViewChild, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { selectScrollY, selectDisplayedElemList } from './path-to-selectors'; import { Observable } from 'rxjs'; @Component({ selector: 'app-scroll-target', templateUrl: './scroll-target.component.html', styleUrls: ['./scroll-target.component.css'] }) export class ScrollTargetComponent implements OnInit { @ViewChild('scrollContainer') scrollContainer!: ElementRef<HTMLElement>; scrollY$: Observable<number>; displayedList$: Observable<any[]>; constructor(private store: Store) { this.scrollY$ = this.store.select(selectScrollY); this.displayedList$ = this.store.select(selectDisplayedElemList); } ngOnInit() { this.scrollY$.subscribe(scrollY => { this.scrollContainer.nativeElement.scrollTop = scrollY; }); } }
5. 关键注意事项
- 确保两个组件的滚动容器样式(高度、overflow等)完全一致,避免同步偏差。
- 若滚动频繁,可添加
debounceTime减少动作触发频率:import { Subject } from 'rxjs'; import { debounceTime } from 'rxjs/operators'; private scrollSubject = new Subject<number>(); ngOnInit() { this.scrollSubject.pipe(debounceTime(30)).subscribe(scrollY => { const scrollData: ListStateModel = { scrollY, displayedElemList: this.displayedList }; this.store.dispatch(getScrollAction({ data: scrollData })); }); } onScroll(event: Event) { const target = event.target as HTMLElement; this.scrollSubject.next(target.scrollTop); } - 在模块中注册Reducer:
import { StoreModule } from '@ngrx/store'; import { scrollReducer } from './path-to-reducer'; @NgModule({ imports: [ // 根模块用forRoot,feature模块用forFeature StoreModule.forRoot({ list: scrollReducer }) ] }) export class AppModule {}
内容的提问来源于stack exchange,提问作者Lelovias
相关产品推荐
相关产品推荐

