构建你的 Pokédex:第 2 部分 - @ngrx/entity 简介 @ngrx/entity 结论更多、更多、更多……

2025-06-11

构建你的 Pokédex:第 2 部分 - @ngrx/entity

介绍

@ngrx/实体

结论

更多,更多,更多……

这篇文章是系列文章的一部分,我将在其中描述如何使用 NGRX 从初学者到忍者构建你的 Pokédex ,如果你想阅读更多内容,你可以阅读以下文章:


封面-1

介绍

在这篇文章中,我们将使用Angular框架和NgRX作为状态管理库来开发一个pokédex 。

建议您了解如何在中级水平上管理 Angular,并了解什么是状态管理库,以便正确理解这篇文章,因为在这个系列中,我们将展示如何开发一个具体的例子(Pokédex),这可以作为您对 NgRX 学习的补充。

首先,沿着这些柱子建造的结果如下面的 GIF 所示。

阅读本文的第一部分对于完全理解正在构建的内容至关重要。在这篇文章中,我们将使用该包改进第一篇文章中开发的代码@ngrx/entity,这将简化创建 Reducer 和 Selector 的任务。

@ngrx/实体

@ngrx/entity包是一个用于管理记录集合的适配器。它提供了操作和查询实体集合的 API。

因此,它简化了创建用于管理模型集合的 Reducer 的样板代码。此外,它还提供了用于管理实体集合的高性能 CRUD 操作。最后,它提供了用于选择实体信息的可扩展类型安全适配器。

第一步是安装可提供所有这些优势的软件包。

npm i @ngrx/entity
Enter fullscreen mode Exit fullscreen mode

实体状态

实体状态是针对给定实体集合的预定义通用接口,具有以下接口:

interface EntityState<V> {
  ids: string[] | number[];
  entities: { [id: string | id: number]: V };
}
Enter fullscreen mode Exit fullscreen mode

上一篇文章中我们开发这两个属性的原因PokemonState如下。通用属性如下:

  • ids . 集合中所有主要 ID 的数组。
  • 实体 (entities)。集合中按主 ID 索引的实体字典。

文件pokemon.state.ts将被替换,pokemon.adapter.ts如下所示。


import { Pokemon } from '@shared/interfaces/pokemon.interface';
export interface PokemonState {
  ids: number[];
  entities: { [key: string]: Pokemon };
}
Enter fullscreen mode Exit fullscreen mode


import { EntityState } from '@ngrx/entity';
import { Pokemon } from '@shared/interfaces/pokemon.interface';
import { createEntityAdapter } from '@ngrx/entity';

export const pokemonAdapter = createEntityAdapter<Pokemon>();

export interface PokemonState extends EntityState<Pokemon> {}
Enter fullscreen mode Exit fullscreen mode

PokemonState在我们的新模型中,我们基于使用继承创建了别名EntityState,尽管我们没有在状态中包含任何新属性。

另一方面,该createEntityAdapter方法负责为我们的Pokemon数据模型创建适配器。该适配器为我们提供了一组方法来修改状态以及创建选择器。

该方法采用具有 2 个属性的对象进行配置。

  • selectId . 用于选择集合主键 ID 的方法。当实体的主键为 id 时,此方法为可选。
  • sortComparer . 用于对集合进行排序的比较函数。仅当集合需要排序后才能显示时才需要使用比较函数。设置为 false 则集合不进行排序,这在 CRUD 操作中可以提高性能。

在我们的例子中,我们没有使用任何配置属性,因为它不是必需的。

Reducers

返回的适配器对象提供了一组方法,可以在您的 reducer 函数中使用,根据您提供的操作来管理实体集合。

  • getInitialState。根据提供的类型返回实体状态的 initialState。initialState 会提供给你的 Reducer 函数。在我们的例子中,这个函数是 内部的包装器pokemonInitialState
export function pokemonInitialState(): PokemonState {
  return pokemonAdapter.getInitialState();
}
Enter fullscreen mode Exit fullscreen mode

适配器集合方法
实体适配器还提供针对实体的操作方法。这些方法可以一次更改一条或多条记录。如果进行了更改,则每个方法返回新的修改状态;如果没有进行更改,则返回相同的状态。

  • addOne:向集合中添加一个实体
  • addMany:将多个实体添加到集合中
  • addAll:用提供的集合替换当前集合
  • removeOne:从集合中删除一个实体
  • removeMany:通过 id 或谓词从集合中删除多个实体
  • removeAll:清除实体集合
  • updateOne:更新集合中的一个实体
  • updateMany:更新集合中的多个实体
  • upsertOne:添加或更新集合中的一个实体
  • upsertMany:添加或更新集合中的多个实体
  • map:通过定义 map 函数来更新集合中的多个实体,类似于 Array.map

这组方法使我们能够简化 reduce 函数,之前我们用复杂的嵌套对象来修改属性。这样,请注意此函数的前后对比,实际上代码已被精简为调用方法。


import { PokemonActionTypes, PokemonActions } from './pokemon.actions';

import { PokemonState } from './pokemon.adapter';

export function pokemonInitialState(): PokemonState {
  return {
    ids: [],
    entities: {}
  };
}

function arrayToObject(array) {
  return array.reduce((obj, item) => {
    obj[item.id] = item;
    return obj;
  }, {});
}

export function pokemonReducer(
  state: PokemonState = pokemonInitialState(),
  action: PokemonActions
): PokemonState {
  switch (action.type) {
    case PokemonActionTypes.LOAD_POKEMONS_SUCCESS:
      return {
        ...state,
        entities: arrayToObject(action.payload)
      };

    case PokemonActionTypes.ADD_SUCCESS:
      return {
        ...state,
        entities: {
          ...state.entities,
          [action.pokemon.id]: action.pokemon
        }
      };

    case PokemonActionTypes.DELETE_SUCCESS:
      const entities = { ...state.entities };
      delete entities[action.id];
      return {
        ...state,
        entities
      };

    case PokemonActionTypes.UPDATE_SUCCESS:
      return {
        ...state,
        entities: {
          ...state.entities,
          [action.pokemon.id]: action.pokemon
        }
      };

    default:
      return state;
  }
}
Enter fullscreen mode Exit fullscreen mode


import { PokemonActionTypes, PokemonActions } from './pokemon.actions';
import { PokemonState, pokemonAdapter } from './pokemon.adapter';

export function pokemonInitialState(): PokemonState {
  return pokemonAdapter.getInitialState();
}

export function pokemonReducer(
  state: PokemonState = pokemonInitialState(),
  action: PokemonActions
): PokemonState {
  switch (action.type) {
    case PokemonActionTypes.LOAD_POKEMONS_SUCCESS:
      return pokemonAdapter.addAll(action.payload, state);

    case PokemonActionTypes.ADD_SUCCESS:
      return pokemonAdapter.addOne(action.pokemon, state);

    case PokemonActionTypes.DELETE_SUCCESS:
      return pokemonAdapter.removeOne(action.id, state);

    case PokemonActionTypes.UPDATE_SUCCESS:
      const { id } = action.pokemon;
      return pokemonAdapter.updateOne(
        {
          id,
          changes: action.pokemon
        },
        state
      );

    default:
      return state;
  }
}

Enter fullscreen mode Exit fullscreen mode

选择器

创建的实体适配器返回的getSelectors方法提供了从实体中选择信息的功能。

因此,有四种最广泛使用的选择器:

export interface EntitySelectors<T, V> {
    selectIds: (state: V) => string[] | number[];
    selectEntities: (state: V) => Dictionary<T>;
    selectAll: (state: V) => T[];
    selectTotal: (state: V) => number;
}
Enter fullscreen mode Exit fullscreen mode

最后,该pokemon.selector.ts文件稍作修改,因为我们不需要构建selectAll选择器,因为我们将使用适配器提供的选择器。


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

import { PokemonState } from './pokemon.adapter';

export const selectPokemonState = createFeatureSelector<PokemonState>(
  'pokemon'
);

export const selectAll = createSelector(
  selectPokemonState,
  state => Object.values(state.entities)
);
Enter fullscreen mode Exit fullscreen mode


import { PokemonState, pokemonAdapter } from './pokemon.adapter';
import { createFeatureSelector, createSelector } from '@ngrx/store';

export const selectPokemonState = createFeatureSelector<PokemonState>(
  'pokemon'
);

export const {
  selectIds,
  selectEntities,
  selectAll,
  selectTotal
} = pokemonAdapter.getSelectors(selectPokemonState);
Enter fullscreen mode Exit fullscreen mode

结论

在本文中,我们使用了该@ngrx/entity包重构了 Pokédex 的小示例。使用适配器可以减少应用程序状态管理中不必要的复杂性。适配器使我们能够轻松地根据需要扩展状态,并且提供了处理应用程序状态时最常用的操作。

因此,在这篇文章中我们讨论了以下主题:

  • 解耦可视化组件的状态管理。
  • 高效、轻松地创建国家管理要素。
  • 创建关注相关内容的组件:视图
  • 自动创建状态,因为使用@ngrx/entity 非常重复。

本系列的以下文章将涵盖一些有趣的主题,例如:

  • 自动创建效果、动作并简化使用减少功能@ngrx/entity
  • 将通过封装使用 Facade 模式@ngrx/data
  • 测试应用程序的状态。

真正重要的是概念,而不是所使用的技术或库。因此,对于那些刚开始构建大型 Angular 应用并需要应用架构原则的人来说,这篇文章应该作为指南。

更多,更多,更多……

这篇文章的GitHub 分支https://github.com/Caballerog/ngrx-pokedex/tree/ngrx-part2

鏂囩珷鏉ユ簮锛�https://dev.to/angular/build-your-pokedex-part-2-ngrx-entity-1cji
PREV
使用指令在 Angular 中创建一个超级简单的 Badge 组件。
NEXT
将您的工作流程迁移到 Linux