36、深入探索 ngrx:状态管理与开发实践

深入探索 ngrx:状态管理与开发实践

1. 引入 ngrx

在开发应用时,我们可以使用 ngrx 来管理应用状态。下面将通过一个具体的应用开发过程,介绍 ngrx 的核心概念和使用方法,包括 ngrx 存储、带负载的动作、reducer、效果和选择器。

2. 使用 ngrx 重构中介应用

我们要重构一个之前的应用,该应用有一个搜索字段和两个链接(eBay 和 Amazon)。重构时,将使用 ngrx 存储替换原来维护应用状态的 SearchService,并添加 ProductService 来生成搜索结果。

新应用将使用以下 ngrx 构建块:
- 存储(Store):用于存储和检索应用状态、搜索查询和结果。
- 减速器(Reducer):处理 SEARCH 和 SEARCH_SUCCESS 类型的动作。
- 效果(Effects):处理 SEARCH 和 SEARCH_SUCCESS 类型的动作。
- 选择器(Selectors):检索整个状态对象、搜索查询或搜索结果。

应用的状态对象包含两个属性:搜索查询和搜索结果。以下是状态对象的类型声明:

export interface State {
  searchQuery: string;
  searchResults: string[];
}
3. 动作(Actions)

在这个应用中,动作可以携带负载。例如,SEARCH 动作可以携带搜索查询作为负载,SEARCH_SUCCESS 动作可以携带搜索结果作为负载。因此,我们将每个动作封装到一个带有构造函数的类中,构造函数接受负载作为参数。动作的声明如下:

import {Action} from '@ngrx/store';
export const SEARCH = '[Product] search';
export const SEARCH_SUCCESS = '[Product] search success';

export class SearchAction implements Action {
  readonly type = SEARCH;
  constructor(public payload: {searchQuery: string}) {}
}

export class SearchSuccessAction implements Action {
  readonly type = SEARCH_SUCCESS;
  constructor(public payload: {searchResults: string[]}) {}
}

export type SearchActions = SearchAction | SearchSuccessAction;

在动作定义中,使用 [Product] 作为前缀,为动作创建了命名空间,使代码更易读。

4. 搜索组件作为动作创建者

SearchComponent 负责在用户输入搜索条件后创建并分发 SEARCH 动作。以下是 SearchComponent 的代码:

import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Store } from '@ngrx/store';
import { SearchAction } from './actions';

@Component({
  selector: 'app-search',
  template: `
    <h2>Search component</h2>
    <input type="text" placeholder="Enter product" [formControl]="searchInput">
  `,
  styles: ['.main {background: yellow}']
})
export class SearchComponent {
  searchInput: FormControl;

  constructor(private store: Store<any>) {
    this.searchInput = new FormControl('');
    this.searchInput.valueChanges
     .pipe(
        debounceTime(300),
        tap(value => console.log(`The user entered ${value}`))
      )
     .subscribe(searchValue => {
        this.store.dispatch(new SearchAction({ searchQuery: searchValue }));
      });
  }
}

分发的动作将被 reducer 捕获,reducer 会更新状态对象中的搜索查询属性。

5. 减速器(Reducer)

reducer 负责根据动作类型更新状态对象。以下是 reducer 的代码:

import {SearchActions, SEARCH, SEARCH_SUCCESS} from './actions';

export interface State {
  searchQuery: string;
  searchResults: string[];
}

const initialState: State = {
  searchQuery: '',
  searchResults: []
};

export function reducer(state = initialState, action: SearchActions): State {
  switch (action.type) {
    case SEARCH: {
      return {
        ...state,
        searchQuery: action.payload.searchQuery,
        searchResults: []
      };
    }
    case SEARCH_SUCCESS: {
      return {
        ...state,
        searchResults: action.payload.searchResults
      };
    }
    default: {
      return state;
    }
  }
}

在 reducer 中,使用 spread 运算符克隆状态对象并更新其属性。

6. 效果(Effects)

在这个应用中,我们有一个效果,使用 ProductService 来获取产品。ProductService 会生成并返回一个包含五个产品的可观察对象,并使用 RxJS 的 delay 运算符模拟一秒的延迟。以下是 ProductService 的代码:

import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/operators';

@Injectable()
export class ProductService {
  static counter = 0;

  getProducts(searchQuery: string): Observable<string[]> {
    const productGenerator = () =>
      `Product ${searchQuery}${ProductService.counter++}`;
    const products = Array.from({length: 5}, productGenerator);
    return of(products).pipe(delay(1000));
  }
}

SearchEffects 类定义了一个效果 loadProducts$ ,用于在接收到 SEARCH 动作后获取产品并分发 SEARCH_SUCCESS 动作。以下是 SearchEffects 的代码:

import { Injectable } from '@angular/core';
import { Actions, Effect } from '@ngrx/effects';
import { Observable } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { ProductService } from './product.service';
import { SearchAction, SearchSuccessAction, SEARCH } from './actions';

@Injectable()
export class SearchEffects {
  @Effect()
  loadProducts$: Observable<any> = this.actions$
   .ofType(SEARCH)
   .pipe(
      map((action: SearchAction) => action.payload),
      switchMap(({searchQuery}) => this.productService.getProducts(searchQuery)),
      map(searchResults => new SearchSuccessAction({searchResults}))
    );

  constructor(private actions$: Actions, private productService: ProductService) {}
}

在这个效果中,使用 ofType(SEARCH) 过滤出 SEARCH 动作,提取其负载并传递给 ProductService 的 getProducts 方法,最后分发 SEARCH_SUCCESS 动作。

7. 选择器(Selectors)

在实际应用中,状态对象可能是一个嵌套属性的树结构,我们可以使用选择器来获取特定的状态切片。以下是选择器的代码:

import {createFeatureSelector, createSelector} from '@ngrx/store';
import {State} from './reducers';

export const getState = createFeatureSelector<State>('myReducer');
export const getSearchQuery = createSelector(getState, state => state.searchQuery);
export const getSearchResults = createSelector(getState, state => state.searchResults);

通过选择器,我们可以方便地获取状态对象的特定属性。

8. 总结已完成的工作
  • 声明了代表 SEARCH 和 SEARCH_RESULTS 类型动作的类。
  • SearchComponent 可以分发 SEARCH 类型的动作。
  • reducer 可以处理这两种动作类型。
  • 声明了可以获取产品并分发 SEARCH_RESULTS 类型动作的效果。
  • 声明了用于获取应用状态切片的选择器。
9. 使用选择器渲染搜索结果

在 eBay 和 Amazon 组件中,使用选择器来渲染搜索条件和检索到的产品。以下是 EbayComponent 的代码:

import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { State, getSearchQuery, getSearchResults } from './reducers';

@Component({
  selector: 'app-ebay',
  template: `
    <div class="ebay">
      <h2>eBay component</h2>
      Search criteria: {{searchFor$ | async}}
      <ul>
        <li *ngFor="let p of searchResults$ | async ">{{ p }}</li>
      </ul>
    </div>
  `,
  styles: ['.ebay {background: cyan}']
})
export class EbayComponent {
  searchFor$ = this.store.select(getSearchQuery);
  searchResults$ = this.store.select(getSearchResults);

  constructor(private store: Store<State>) {}
}
10. 注册存储和效果

最后,需要在应用模块中注册存储和效果。以下是应用模块的代码:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { environment } from '../environments/environment';
import { AppComponent } from './app.component';
import { EbayComponent } from './ebay.component';
import { AmazonComponent } from './amazon.component';
import { SearchComponent } from './search.component';
import { reducer } from './reducers';
import { SearchEffects } from './effects';
import { ProductService } from './product.service';
import { HashLocationStrategy, LocationStrategy } from '@angular/common';

@NgModule({
  imports: [
    BrowserModule,
    CommonModule,
    ReactiveFormsModule,
    RouterModule.forRoot([
      {path: '', component: EbayComponent},
      {path: 'amazon', component: AmazonComponent}
    ]),
    StoreModule.forRoot({myReducer: reducer}),
    EffectsModule.forRoot([SearchEffects]),
    StoreDevtoolsModule.instrument({
      logOnly: environment.production
    })
  ],
  declarations: [AppComponent, EbayComponent, AmazonComponent, SearchComponent],
  providers: [
    ProductService,
    {provide: LocationStrategy, useClass: HashLocationStrategy}
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

以下是整个应用的流程 mermaid 流程图:

graph LR
    A[用户输入搜索条件] --> B[SearchComponent 分发 SEARCH 动作]
    B --> C[Reducer 更新搜索查询]
    C --> D[SearchEffects 监听 SEARCH 动作]
    D --> E[ProductService 获取产品]
    E --> F[SearchEffects 分发 SEARCH_SUCCESS 动作]
    F --> G[Reducer 更新搜索结果]
    G --> H[EbayComponent 和 AmazonComponent 使用选择器获取结果并渲染]

为了更清晰地展示各部分的关系,我们可以用表格总结:
| 组件/模块 | 功能 |
| ---- | ---- |
| SearchComponent | 接收用户输入,分发 SEARCH 动作 |
| Reducer | 根据动作类型更新状态对象 |
| SearchEffects | 监听 SEARCH 动作,调用 ProductService 获取产品,分发 SEARCH_SUCCESS 动作 |
| ProductService | 生成并返回产品的可观察对象 |
| EbayComponent 和 AmazonComponent | 使用选择器获取搜索结果并渲染 |
| AppModule | 注册存储、效果和路由等 |

深入探索 ngrx:状态管理与开发实践

11. ngrx 其他可用功能

除了上述使用的 @ngrx/store @ngrx/effects 包,ngrx 还有其他一些有用的包:
- @ngrx/entity :是一个实体状态适配器,用于管理记录集合。
- @ngrx/schematics :是一个脚手架库,提供生成与 ngrx 相关代码的蓝图。

12. 使用 Redux DevTools 监控状态

由于将状态管理操作委托给了 ngrx,因此需要一个工具来在运行时监控状态变化。可以使用浏览器扩展 Redux DevTools 以及 @ngrx/store-devtools 包来对应用状态进行检测。具体操作步骤如下:
1. 安装 @ngrx/store-devtools

npm install @ngrx/store-devtools
  1. 添加 Chrome 扩展 Redux DevTools :在 Chrome 浏览器的扩展商店中搜索并添加该扩展,Firefox 浏览器也有相应的扩展。
  2. 在应用模块中添加检测代码
    • 默认配置 :在 @NgModule 装饰器的 imports 部分添加以下代码:
StoreDevtoolsModule.instrument()
- **生产环境优化配置**:为了在生产环境中减少性能开销,可以使用环境变量:
StoreDevtoolsModule.instrument({
  logOnly: environment.production
})

在生产环境中,将 logOnly 标志设置为 true ,这样可以关闭一些会引入明显性能开销的功能,如分发和重新排序动作、在页面重新加载之间持久化状态和动作历史等。完整的禁用功能列表可查看 http://mng.bz/cOwC

- **自定义配置示例**:以下代码允许监控最多 25 个最近的动作,并在生产环境中以仅日志模式工作:
StoreDevtoolsModule.instrument({
  maxAge: 25,
  logOnly: environment.production
})

还可以通过为 instrument() 方法提供 features 参数来限制 Chrome Redux 扩展的某些功能。更多关于配置 ngrx 检测和支持的 API 的详细信息可查看 http://mng.bz/3AXe

13. Redux DevTools 功能展示
  • 初始状态 :启动应用后,Chrome Redux 面板可能会显示一个黑色窗口并提示 “No store found”,此时刷新页面即可。初始状态下,会有两个由 @ngrx/store @ngrx/effects 内部分发的 init 动作。选择 @ngrx/store/init 动作和右上角的 State 按钮,可以看到状态属性 searchQuery searchResults 为空。
  • 查看动作信息 :点击右上角的 Action 按钮,可以看到最新动作的类型和负载。例如,最新动作可能是 [Product] search success ,动作负载存储在状态属性 searchResults 中。
  • 查看状态信息 :点击 State 按钮,可以看到状态变量 searchQuery searchResults 的当前值。
  • 查看状态差异 :如果 State 标签显示整个状态对象,点击 Diff 按钮可以显示特定动作导致的状态变化。如果没有选择动作, Diff 标签将显示最新动作引起的状态变化。
  • 时间旅行调试 :在调试应用时,开发人员经常需要重新创建应用的特定状态。使用 Redux DevTools 可以在不刷新页面的情况下进行时间旅行,重新创建特定状态。选择一个动作后,会看到 Jump Skip 按钮。点击 Skip 会划掉所选动作,运行中的应用会反映此更改;点击 Sweep 按钮会将该动作从列表中移除;点击 Jump 按钮会跳转到所选动作对应的应用状态,Redux DevTools 会显示该时刻的状态属性,应用的 UI 也会相应地重新渲染。

下面用 mermaid 流程图展示使用 Redux DevTools 的流程:

graph LR
    A[安装 @ngrx/store-devtools] --> B[添加 Chrome 扩展 Redux DevTools]
    B --> C[在应用模块添加检测代码]
    C --> D[启动应用并刷新页面]
    D --> E[查看初始状态]
    E --> F[点击 Action 按钮查看动作信息]
    F --> G[点击 State 按钮查看状态信息]
    G --> H[点击 Diff 按钮查看状态差异]
    H --> I[选择动作进行时间旅行调试]

为了更清晰地展示 Redux DevTools 的操作和功能,我们用表格总结:
| 操作 | 功能 |
| ---- | ---- |
| 安装 @ngrx/store-devtools | 为应用添加状态检测功能 |
| 添加 Chrome 扩展 Redux DevTools | 提供可视化界面监控状态 |
| 在应用模块添加检测代码 | 配置检测功能 |
| 刷新页面 | 解决 “No store found” 问题 |
| 点击 Action 按钮 | 查看最新动作的类型和负载 |
| 点击 State 按钮 | 查看状态变量的当前值 |
| 点击 Diff 按钮 | 查看特定动作导致的状态变化 |
| 选择动作后点击 Skip | 划掉所选动作,应用反映更改 |
| 选择动作后点击 Sweep | 将动作从列表中移除 |
| 选择动作后点击 Jump | 跳转到所选动作对应的应用状态 |

通过以上对 ngrx 的介绍和实践,我们可以看到 ngrx 为 Angular 应用的状态管理提供了强大而灵活的解决方案,结合 Redux DevTools 可以更方便地进行调试和监控。希望这些内容能帮助你更好地使用 ngrx 来管理应用状态。

(Mathcad+Simulink仿真)基于扩展描述函数法的LLC谐振变换器小信号分析设计内容概要:本文围绕“基于扩展描述函数法的LLC谐振变换器小信号分析设计”展开,结合MathcadSimulink仿真工具,系统研究LLC谐振变换器的小信号建模方法。重点利用扩展描述函数法(Extended Describing Function Method, EDF)对LLC变换器在非线性工作条件下的动态特性进行线性化近似,建立适用于频域分析的小信号模型,并通过Simulink仿真验证模型准确性。文中详细阐述了建模理论推导过程,包括谐振腔参数计算、开关网络等效处理、工作模态分析及频响特性提取,最后通过仿真对比验证了该方法在稳定性分析控制器设计中的有效性。; 适合人群:具备电力电子、自动控制理论基础,熟悉Matlab/Simulink和Mathcad工具,从事开关电源、DC-DC变换器或新能源变换系统研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①掌握LLC谐振变换器的小信号建模难点解决方案;②学习扩展描述函数法在非线性系统线性化中的应用;③实现高频LLC变换器的环路补偿稳定性设计;④结合Mathcad进行公式推导参数计算,利用Simulink完成动态仿真验证。; 阅读建议:建议读者结合Mathcad中的数学推导Simulink仿真模型同步学习,重点关注EDF法的假设条件适用范围,动手复现建模步骤和频域分析过程,以深入理解LLC变换器的小信号行为及其在实际控制系统设计中的应用。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值