如何将 updateItem 应用于 Ngxs 中的 multiple/all 个项目?
How to apply updateItem to multiple/all items in Ngxs?
我需要一个替代方法来为 Ngxs 中数组中的所有项目更改具有相同值的相同属性。
我尝试使用 updateItem(() => true, patch({ ... }))
,但它不起作用,因为第一个函数 () => true
只是 returns 第一个匹配项,最终只更新了一个项目。
以下是我尝试做的简化示例:
import { State, Action, StateContext } from '@ngxs/store';
import { patch, append, removeItem, insertItem, updateItem } from '@ngxs/store/operators';
export interface AnimalsStateModel {
others: { /*...*/ }[];
rabbits: Rabbit[];
}
export interface Rabbit {
name: string,
alive: boolean,
}
export class ChangeRabbitAlive {
static readonly type = '[Animals] Change rabbit alive';
constructor(public payload: { name: string; alive: boolean }) {}
}
export class ChangeAllRabbitsDead {
static readonly type = '[Animals] Change all rabbits dead';
constructor(public payload: { }) {}
}
@State<AnimalsStateModel>({
name: 'animals',
defaults: {
others: [ /*...*/ ],
rabbits: [
{ name: 'Michael', alive: true },
{ name: 'John', alive: true },
{ name: 'Jimmy', alive: true },
{ name: 'Jake', alive: true },
{ name: 'Alan', alive: true },
]
}
})
export class AnimalsState {
@Action(ChangeRabbitAlive)
changeRabbitAlive(ctx: StateContext<AnimalsStateModel>, { payload }: ChangeRabbitAlive) {
ctx.setState(
patch({
rabbits: updateItem<Rabbit>(rabbit => rabbit.name === payload.name, patch({ alive: payload.alive }))
})
);
}
@Action(ChangeAllRabbitsDead)
changeAllRabbitsDead(ctx: StateContext<AnimalsStateModel>, { payload }: ChangeAllRabbitsDead) {
const isAlive = { alive: false };
ctx.setState(
patch({
//rabbits: updateItem<Rabbit>(name => true, patch({ alive: false }))// Type 'boolean' is not assignable to type 'false'
rabbits: updateItem<Rabbit>(() => true, patch(isAlive))
})
);
}
}
有没有什么办法可以一次杀光所有的兔子?
额外:如果您能解释为什么在这种情况下会出现错误“Type 'boolean' is not assignable to type 'false'”,我们将不胜感激。
不确定这是否是最漂亮的方式,但从概念上讲:
let state2 = {
...state,
rabbits: state.rabbits.map(x => {
return {
name: x.name,
alive: false
}
})
}
您可以按照 NGXS 文档中的说明创建一个 custom state operator 来实现。
您可以尝试以下操作:
import { StateOperator } from '@ngxs/store';
import { isStateOperator } from '@ngxs/store/operators';
import { Predicate } from '@ngxs/store/operators/internals';
/**
* @param selector - Array of indexes or a predicate function
* that can be provided in `Array.prototype.findIndex`
* @param operatorOrValue - New value under the `selector` index or a
* function that can be applied to an existing value
*/
export function updateItems<T>(
selector: number[] | Predicate<T>,
operatorOrValue: Partial<T> | StateOperator<T>
): StateOperator<T[]> {
return function updateItemsOperator(existing: T[]) {
let indexes = [];
if (!selector || !operatorOrValue || !existing) {
return existing;
}
if (isPredicate(selector)) {
indexes = findIndexes(selector, existing);
} else if (isArrayNumber(selector)) {
indexes = selector;
}
if (invalidIndexes(indexes, existing)) {
return existing;
}
let values: Record<number, T> = {};
if (isStateOperator(operatorOrValue)) {
values = indexes.reduce(
(acc, it) => ({ ...acc, [it]: operatorOrValue(existing[it]) }),
{}
);
} else {
values = indexes.reduce(
(acc, it) =>
isObject(existing[it])
? { ...acc, [it]: { ...existing[it], ...operatorOrValue } }
: { ...acc, [it]: operatorOrValue },
{}
);
}
const clone = [...existing];
const keys = Object.keys(values);
for (const i in keys) {
clone[keys[i]] = values[keys[i]];
}
return clone;
};
}
function isObject(value: any): boolean {
return typeof value === 'object';
}
function isPredicate<T>(
value: Predicate<T> | boolean | number | number[]
): value is Predicate<T> {
return typeof value === 'function';
}
function isArrayNumber(value: any[]): boolean {
for (const i in value) {
if (isNaN(value[i])) {
return false;
}
}
return true;
}
function invalidIndex(index: number): boolean {
return isNaN(index) || index < 0;
}
function invalidIndexes<T>(
indexes: number[],
existing: Readonly<T[]>
): boolean {
for (const i in indexes) {
if (
!existing ||
existing?.length - 1 < indexes[i] ||
isNaN(indexes[i]) ||
invalidIndex(indexes[i])
) {
return true;
}
}
return false;
}
function findIndexes<T>(
predicate: Predicate<T>,
existing: Readonly<T[]>
): number[] {
return existing.reduce((acc, it, i) => {
const index = predicate(it as any) ? i : -1;
return invalidIndex(index) ? acc : [...acc, index];
}, []);
}
我发现这个线程 [DOCS]: Share your custom State Operators here! 提到了自定义运算符 updateManyItems
,这显然是我要找的。但是,它在通用库中不可用,我也找不到它的实现。所以我尝试了一些混合基本运算符的替代方案,并使用 compose
:
提出了一个简单的解决方案
要全部更新它们,只需为列表中的每个索引添加一个 updateItem
:
const isAlive = { alive: false };
const updateItems = ctx.getState().rabbits.map((r, i) => updateItem<Rabbit>(i, patch(isAlive)));
ctx.setState(
patch({
rabbits: compose(...updateItems),
})
);
要更新部分项目,只需添加 updateItem
按项目的某些唯一标识符进行过滤:
const isAlive = { alive: false };
const updateItems = payload.rabbitNames.map((rn) => updateItem<Rabbit>(rabbit => rabbit.name === rn, patch(isAlive)));
ctx.setState(
patch({
rabbits: compose(...updateItems),
})
);
如果项目标识符在列表中可能重复,也可以按索引更新:
const isAlive = { alive: false };
const indexList = ctx.getState().rabbits.reduce((result, r, i) =>
(payload.rabbitNames.includes(r.name) ? result.push(i) : null, result) , []);
const updateItems = indexList.map((i) => updateItem<Rabbit>(i, patch(isAlive)));
ctx.setState(
patch({
rabbits: compose(...updateItems),
})
);
可以更好地优化本机运算符,但这些替代方案仍然可以正常工作。
我需要一个替代方法来为 Ngxs 中数组中的所有项目更改具有相同值的相同属性。
我尝试使用 updateItem(() => true, patch({ ... }))
,但它不起作用,因为第一个函数 () => true
只是 returns 第一个匹配项,最终只更新了一个项目。
以下是我尝试做的简化示例:
import { State, Action, StateContext } from '@ngxs/store';
import { patch, append, removeItem, insertItem, updateItem } from '@ngxs/store/operators';
export interface AnimalsStateModel {
others: { /*...*/ }[];
rabbits: Rabbit[];
}
export interface Rabbit {
name: string,
alive: boolean,
}
export class ChangeRabbitAlive {
static readonly type = '[Animals] Change rabbit alive';
constructor(public payload: { name: string; alive: boolean }) {}
}
export class ChangeAllRabbitsDead {
static readonly type = '[Animals] Change all rabbits dead';
constructor(public payload: { }) {}
}
@State<AnimalsStateModel>({
name: 'animals',
defaults: {
others: [ /*...*/ ],
rabbits: [
{ name: 'Michael', alive: true },
{ name: 'John', alive: true },
{ name: 'Jimmy', alive: true },
{ name: 'Jake', alive: true },
{ name: 'Alan', alive: true },
]
}
})
export class AnimalsState {
@Action(ChangeRabbitAlive)
changeRabbitAlive(ctx: StateContext<AnimalsStateModel>, { payload }: ChangeRabbitAlive) {
ctx.setState(
patch({
rabbits: updateItem<Rabbit>(rabbit => rabbit.name === payload.name, patch({ alive: payload.alive }))
})
);
}
@Action(ChangeAllRabbitsDead)
changeAllRabbitsDead(ctx: StateContext<AnimalsStateModel>, { payload }: ChangeAllRabbitsDead) {
const isAlive = { alive: false };
ctx.setState(
patch({
//rabbits: updateItem<Rabbit>(name => true, patch({ alive: false }))// Type 'boolean' is not assignable to type 'false'
rabbits: updateItem<Rabbit>(() => true, patch(isAlive))
})
);
}
}
有没有什么办法可以一次杀光所有的兔子?
额外:如果您能解释为什么在这种情况下会出现错误“Type 'boolean' is not assignable to type 'false'”,我们将不胜感激。
不确定这是否是最漂亮的方式,但从概念上讲:
let state2 = {
...state,
rabbits: state.rabbits.map(x => {
return {
name: x.name,
alive: false
}
})
}
您可以按照 NGXS 文档中的说明创建一个 custom state operator 来实现。
您可以尝试以下操作:
import { StateOperator } from '@ngxs/store';
import { isStateOperator } from '@ngxs/store/operators';
import { Predicate } from '@ngxs/store/operators/internals';
/**
* @param selector - Array of indexes or a predicate function
* that can be provided in `Array.prototype.findIndex`
* @param operatorOrValue - New value under the `selector` index or a
* function that can be applied to an existing value
*/
export function updateItems<T>(
selector: number[] | Predicate<T>,
operatorOrValue: Partial<T> | StateOperator<T>
): StateOperator<T[]> {
return function updateItemsOperator(existing: T[]) {
let indexes = [];
if (!selector || !operatorOrValue || !existing) {
return existing;
}
if (isPredicate(selector)) {
indexes = findIndexes(selector, existing);
} else if (isArrayNumber(selector)) {
indexes = selector;
}
if (invalidIndexes(indexes, existing)) {
return existing;
}
let values: Record<number, T> = {};
if (isStateOperator(operatorOrValue)) {
values = indexes.reduce(
(acc, it) => ({ ...acc, [it]: operatorOrValue(existing[it]) }),
{}
);
} else {
values = indexes.reduce(
(acc, it) =>
isObject(existing[it])
? { ...acc, [it]: { ...existing[it], ...operatorOrValue } }
: { ...acc, [it]: operatorOrValue },
{}
);
}
const clone = [...existing];
const keys = Object.keys(values);
for (const i in keys) {
clone[keys[i]] = values[keys[i]];
}
return clone;
};
}
function isObject(value: any): boolean {
return typeof value === 'object';
}
function isPredicate<T>(
value: Predicate<T> | boolean | number | number[]
): value is Predicate<T> {
return typeof value === 'function';
}
function isArrayNumber(value: any[]): boolean {
for (const i in value) {
if (isNaN(value[i])) {
return false;
}
}
return true;
}
function invalidIndex(index: number): boolean {
return isNaN(index) || index < 0;
}
function invalidIndexes<T>(
indexes: number[],
existing: Readonly<T[]>
): boolean {
for (const i in indexes) {
if (
!existing ||
existing?.length - 1 < indexes[i] ||
isNaN(indexes[i]) ||
invalidIndex(indexes[i])
) {
return true;
}
}
return false;
}
function findIndexes<T>(
predicate: Predicate<T>,
existing: Readonly<T[]>
): number[] {
return existing.reduce((acc, it, i) => {
const index = predicate(it as any) ? i : -1;
return invalidIndex(index) ? acc : [...acc, index];
}, []);
}
我发现这个线程 [DOCS]: Share your custom State Operators here! 提到了自定义运算符 updateManyItems
,这显然是我要找的。但是,它在通用库中不可用,我也找不到它的实现。所以我尝试了一些混合基本运算符的替代方案,并使用 compose
:
要全部更新它们,只需为列表中的每个索引添加一个 updateItem
:
const isAlive = { alive: false };
const updateItems = ctx.getState().rabbits.map((r, i) => updateItem<Rabbit>(i, patch(isAlive)));
ctx.setState(
patch({
rabbits: compose(...updateItems),
})
);
要更新部分项目,只需添加 updateItem
按项目的某些唯一标识符进行过滤:
const isAlive = { alive: false };
const updateItems = payload.rabbitNames.map((rn) => updateItem<Rabbit>(rabbit => rabbit.name === rn, patch(isAlive)));
ctx.setState(
patch({
rabbits: compose(...updateItems),
})
);
如果项目标识符在列表中可能重复,也可以按索引更新:
const isAlive = { alive: false };
const indexList = ctx.getState().rabbits.reduce((result, r, i) =>
(payload.rabbitNames.includes(r.name) ? result.push(i) : null, result) , []);
const updateItems = indexList.map((i) => updateItem<Rabbit>(i, patch(isAlive)));
ctx.setState(
patch({
rabbits: compose(...updateItems),
})
);
可以更好地优化本机运算符,但这些替代方案仍然可以正常工作。