我已经编写了以下reducer来将状态项存储在我的Angular 2应用中。这些项目是金融工具的价格报价(例如股票/货币)。
我的Reducer实施如下:
export const offersStore = (state = new Array<Offer>(), action:Action) => {
switch(action.type){
case "Insert":
return [
...state, action.payload
];
case "Update":
return state.map(offer => {
if(offer.Instrument === action.payload.Instrument)
{
return Object.assign({}, action.payload);
}
else return offer;
});
case "Delete":
return state.filter(offer => offer.Instrument !== action.payload )
default:
return state;
}
}
我设法让插入,更新和删除工作 - 虽然这并不容易。我觉得Redux与我多年来的编码方式有所不同。
我的应用程序上有一个仪器组件/页面 - 显示一个特定仪器的所有可用信息,由InstrumentId指示,例如: &#34; EUR / USD&#34; (存储在payload.Instrument属性中)。
我的问题是,我不确定如何有效地搜索特定的工具并将其从商店中取出。不仅如此,如果商店中的仪器经常通过来自服务器的websocket push更新,我还希望更新我获取的仪器。所以我真的需要在商店中搜索特定的工具,然后将其作为Observable返回,它将继续根据推送到商店的新数据更新View Component。
我怎样才能做到这一点?
答案 0 :(得分:12)
您没有得到的是,对于在reducer上调用的每个操作,都会返回新状态。
根据问题中的示例代码,您的州只是一个工具列表 没有索引,因此检查工具是否在列表中的唯一方法是搜索整个列表。
但是,如果你的州是一本字典怎么办?如果你将一个索引列表分别保存到字典中怎么办?
您的州类型是:
export interface OfferState {
ids: string[];
entities: { [id: string]: IOffer };
};
每当执行某个操作时,都会返回新状态。它是Redux中的一个重要概念,因为状态永远不会直接变异。当你编写减速器时,你实际上最好严格执行这个:(比如说你已经“提供减速器”和另一个减速器,你将它们组合成一个compose:
> export default compose(storeFreeze, combineReducers) ({ oether:
> otherReducer, offers: offersReducer });
在Redux中容易出错 - 但如果你试图直接改变状态,使用storeFreeze会抛出一个错误。关键是行动改变状态,并创造一个新的状态。它们不会改变现有的状态 - 它可以让我们撤消/重做......等等。
使用上面的例子,我会将其用作我的Offer的减速器:
export interface OfferState {
ids: string[];
entities: { [id: string]: IOffer };
};
export default function(state = initialState, action: Action): OfferState {
switch(action.type){
case OfferActions.INSERT:
const offer : IOffer = action.payload;
return {
ids: [ ...state.ids, action.payload.Instrument ],
entities: Object.assign({}, state.entities, { [action.payload.Instrument]: action.payload})
};
case OfferActions.UPDATE:
return {
ids: [...state.ids],
entities: Object.assign({}, state.entities, { [action.payload.Instrument]: action.payload})
}
default:
return state;
}
}
请注意,通过object.assign(深层复制)对临时状态进行更改,然后返回新状态。
这个问题的另一个答案有点令人困惑。它详细介绍了如何组合不同的减速器,但对我来说没有多大意义。
你的redurs / index.ts中的你应该有一个类型:
export interface AppState {
otherReducer: OtherReducer;
offers: fromOffersReducer.OfferState;
}
在这个index.ts中,你应该有能够获得reducers的函数:
export function getOfferState() {
return (state$: Observable<AppState>) => state$
.select(s => s.offers);
}
export function getOtherReducer() {
return (state$ : Observable<AppState>) => state$
.select(s => s.otherReducer)
}
在我们的offerReducer和我们的其他Rededcer中,我们定义了可以查询我们需要的数据的函数。这些是匿名函数,目前没有链接到任何东西,但我们稍后会链接它们(到getReducerFunctions)。
这些功能的例子:
export function getOfferEntities() {
return (state$: Observable<OfferState>) => state$
.select(s => s.entities);
};
export function getOffer(id: string) {
return (state$: Observable<OfferState>) => state$
.select(s => s.entities[id]);
}
这没有任何作用。除非我们将它应用于我们制作得更早的一些有用的数据(例如offerRedeucer),我们将这两个结合起来:
import offersReducer, * as fromOffersReducer from './OffersReducer';
export function getOfferEntities() {
return compose(fromOffersReducer.getOfferEntities(), getOfferState());
}
export function getOffer(instrument:string) {
return compose(fromOffersReducer.getOffer(instrument), getOfferState());
}
我试图预测你将面临的下一个问题(第二个减速机)以及回答“你如何得到一个项目?”的问题。因为我知道你几乎会立刻偶然发现这个问题。但是不要让你把你的原始查询混淆....找到一个项目的诀窍就是维持一个状态,这是一个项目的地图(一个字典) - 从不改变那个状态,总是返回一个新的状态。遵循该指导,您的问题就解决了。您将面临的下一个问题是多个减速器,但在上面再次阅读它应该是有意义的。
我不同意之前关于stackoverflow问题的答案 - 它们是你需要它们的任何东西。如果您需要教程,那么它们可以作为教程。而且我认为这不是一件坏事 - 尤其是像RXJS这样的小众科目,其中提供的建议和文档很少。
感谢你对这个问题给予了赏心悦目 - 我希望我能给你一个更好的答案。任何进一步的问题 - 只是大叫。我很乐意提供帮助。你不会得到“我不愿意回答你的任何问题”......我来这里是为了帮助 - 不要侮辱。
答案 1 :(得分:8)
好的,我会试着解释一下设置它的方法,并希望以你和其他人可以理解的方式做到这一点。
因此,如果您要存储一个对象数组并且需要通过某个id或键访问某行,那么ngrx示例应用程序显示您可以执行此操作的方式是创建一个包含您将使用的对象的对象(在他们的书app)的情况下,书的id作为属性键。您可以将任何字符串设置为属性名称。所以说你的状态有一个名为“实体”的属性,其值是一个空白对象。然后,您可以获取阵列并在“实体”对象上创建属性。
export interface BooksState {
entities: { [id: string]: Book };
};
让我们从book对象数组中的一行开始。要将此行添加到“实体”对象,您可以这样做。
reducerState.entities[book.id] = book;
以上将使本书的id成为“实体”对象的属性 如果您要在控制台或调试工具中检查它,它可能在创建后看起来像这样。
reducerState.entities.15: { id: 15, name: "To Kill a Mockingbird" }
ngrx示例应用程序在完整数组上运行,使用javascript数组上的reduce运算符将其添加到状态。
const newBookEntities
= newBooks.reduce(
(entities: { [id: string]: Book }, book: Book) =>
{
return Object.assign(entities, { [book.id]: book });
}, {});
然后创建特殊函数以获取此状态的某些部分,这些部分可以在以后组合在一起以获取所需的特定行
export function getBooksState() {
return (state$: Observable<AppState>) => state$
.select(s => s.books);
}
export function getBookEntities() {
return (state$: Observable<BooksState>) => state$
.select(s => s.entities);
};
export function getBooks(bookIds: string[]) {
return (state$: Observable<BooksState>) => state$
.let(getBookEntities())
.map(entities => bookIds.map(id => entities[id]));
}
他们在示例
中的index.ts桶中组成了这些import { compose } from '@ngrx/core/compose';
export function getBooks(bookIds: string[]) {
return compose(fromBooks.getBooks(bookIds), getBooksState());
}
此功能将从右到左将呼叫链接在一起。首先调用getBooksState来获取reducer状态,然后调用fromBooks.getBooks并传入你想要的bookid和前一个“getBooksState”中的状态,并允许你映射到你需要的所选状态。
在您的组件中,您可以导入此功能并使用它来获取您想要的书籍。
myBooks$: Observable<Books> = this.store.let(getBooks(bookIds)));
每次更新商店时,都会更新此返回的observable。
已添加:因此,商店对象上的运算符“let”将当前observable传递给将“商店”状态对象保存到“getBooks”函数返回的函数中。
让我们仔细看看。
export function getBooks(bookIds: string[]) {
return compose(fromBooks.getBooks(bookIds), getBooksState());
}
这里有两个函数传递给compose函数 - fromBooks.getBooks(bookIds)和getBooksState()。 compose函数允许将值传递到右边的第一个函数中,并将函数结果传递给左边的函数,依此类推,依此类推。所以在这种情况下,我们将获取“getBooksState”返回的函数的结果,并将其传递给函数“fromBooks.getBooks(bookIds)”返回的函数,然后最终返回该结果。
这两个函数包含一个Observable。在getBooksState()返回的函数的情况下,它接受存储状态可观察对象作为参数,它将从中映射/选择(映射和选择彼此的别名)到我们关心的商店的切片并返回新映射的observable。映射的observable将传递给fromBooks.getBooks(bookIds)函数返回的函数。此函数需要状态切片可观察。此函数仅通过拉出我们关心的实体来执行新映射。这个新的observable是“store.let”调用最终返回的内容。
如果您需要更多说明,请提出问题,我会尽力澄清。