构建你的 Pokédex:第 2 部分 - @ngrx/entity
介绍
@ngrx/实体
结论
更多,更多,更多……
这篇文章是系列文章的一部分,我将在其中描述如何使用 NGRX 从初学者到忍者构建你的 Pokédex ,如果你想阅读更多内容,你可以阅读以下文章:
- 第一部分:构建你的宝可梦图鉴:NGRX 简介
- 第 2 部分:构建你的 Pokédex:@ngrx/entity
- 第 3 部分:构建你的 Pokédex:使用 create* 函数改进 NgRX
- 第 4 部分:构建你的 Pokédex:@ngrx/data
- 第 5 部分:构建你的 Pokédex:测试 NgRX
介绍
在这篇文章中,我们将使用Angular框架和NgRX作为状态管理库来开发一个pokédex 。
建议您了解如何在中级水平上管理 Angular,并了解什么是状态管理库,以便正确理解这篇文章,因为在这个系列中,我们将展示如何开发一个具体的例子(Pokédex),这可以作为您对 NgRX 学习的补充。
首先,沿着这些柱子建造的结果如下面的 GIF 所示。
阅读本文的第一部分对于完全理解正在构建的内容至关重要。在这篇文章中,我们将使用该包改进第一篇文章中开发的代码@ngrx/entity
,这将简化创建 Reducer 和 Selector 的任务。
@ngrx/实体
该@ngrx/entity
包是一个用于管理记录集合的适配器。它提供了操作和查询实体集合的 API。
因此,它简化了创建用于管理模型集合的 Reducer 的样板代码。此外,它还提供了用于管理实体集合的高性能 CRUD 操作。最后,它提供了用于选择实体信息的可扩展类型安全适配器。
第一步是安装可提供所有这些优势的软件包。
npm i @ngrx/entity
实体状态
实体状态是针对给定实体集合的预定义通用接口,具有以下接口:
interface EntityState<V> {
ids: string[] | number[];
entities: { [id: string | id: number]: V };
}
上一篇文章中我们开发这两个属性的原因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 };
}
后
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> {}
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();
}
适配器集合方法
实体适配器还提供针对实体的操作方法。这些方法可以一次更改一条或多条记录。如果进行了更改,则每个方法返回新的修改状态;如果没有进行更改,则返回相同的状态。
- 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;
}
}
后
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;
}
}
选择器
创建的实体适配器返回的getSelectors方法提供了从实体中选择信息的功能。
因此,有四种最广泛使用的选择器:
export interface EntitySelectors<T, V> {
selectIds: (state: V) => string[] | number[];
selectEntities: (state: V) => Dictionary<T>;
selectAll: (state: V) => T[];
selectTotal: (state: V) => number;
}
最后,该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)
);
后
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);
结论
在本文中,我们使用了该@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