typescript 从 ngrx/store 获取单个项目
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/38957510/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
get single item from ngrx/store
提问by reach4thelasers
I've written the following reducer to store the state items in my Angular 2 app. The Items are price offers for Financial Instruments (e.g. stocks/currencies).
我编写了以下 reducer 来在我的 Angular 2 应用程序中存储状态项。项目是金融工具(例如股票/货币)的报价。
My Reducer Implementation is as follows:
我的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;
}
}
}
I managed to get Inserts, Updates and Deletes working - although it wasn't easy. I find Redux to be something of a paradigm shift away from how I've been coding for years.
我设法让插入、更新和删除工作 - 尽管这并不容易。我发现 Redux 是我多年来编码方式的一种范式转变。
I've got an Instrument Component/Page on my App - which shows all available information for one specific Instrument, indicated by InstrumentId e.g. "EUR/USD" (stored in the payload.Instrument property).
我的应用程序上有一个仪器组件/页面 - 它显示了一个特定仪器的所有可用信息,由 InstrumentId 指示,例如“EUR/USD”(存储在 payload.Instrument 属性中)。
My problem is, I'm not sure how to efficiently search for a specific instrument and grab it out of the store. Not only this, but I also want the instrument I fetch to be updated if the Instrument in the store is updated as they are frequently via websocket push from the server. So I really need to search the store for a specific instrument, and return it as an Observable, that will continue to update the View Component based on new data that gets pushed to the store.
我的问题是,我不确定如何有效地搜索特定乐器并将其从商店中取出。不仅如此,如果商店中的乐器更新,我还希望我获取的乐器更新,因为它们经常通过来自服务器的 websocket 推送进行更新。所以我真的需要在商店中搜索特定的乐器,并将其作为 Observable 返回,这将继续根据推送到商店的新数据更新视图组件。
How can I achieve this?
我怎样才能做到这一点?
回答by reach4thelasers
For every action that is called on a reducer, the new state is returned.
对于在减速器上调用的每个操作,都会返回新状态。
From the example code in the question, state is just a list of instruments.
There's no index, so the only way to check if an instrument is in the list is to search the whole list.
从问题中的示例代码来看,状态只是工具列表。
没有索引,因此检查工具是否在列表中的唯一方法是搜索整个列表。
But what if your state was a dictionary? Furthermore, what if you kept a list of indexes seperate to the dictionary?
但是如果你的状态是一本字典呢?此外,如果您将索引列表与字典分开保存会怎样?
your state type is this:
你的状态类型是这样的:
export interface OfferState {
ids: string[];
entities: { [id: string]: IOffer };
};
Any time an action is executed, the new state is returned. It is an important concept in Redux, because state can never be mutated directly. You're actually best strictly enforcing this when you compose your reducer: (say you've got you "offers reducer" and another reducer, you combine them to one with compose:
每次执行操作时,都会返回新状态。它是 Redux 中的一个重要概念,因为状态永远不能直接改变。实际上,您最好在编写减速器时严格执行这一点:(假设您有“提供减速器”和另一个减速器,您可以将它们与 compose 结合起来:
> export default compose(storeFreeze, combineReducers) ({ oether:
> otherReducer, offers: offersReducer });
Its easy to do things wrong in Redux - but using storeFreeze will throw up an error if you try to mutate the state directly. The point is that actions change state, and make a new state. They don't change the existing state - it lets us undo/redo... etc.
在 Redux 中很容易做错事——但是如果您尝试直接改变状态,使用 storeFreeze 会引发错误。关键是动作会改变状态,并创建一个新的状态。它们不会改变现有状态——它让我们可以撤消/重做……等等。
Using your example above I would use this as my Offer's reducer:
使用上面的示例,我会将其用作我的 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;
}
}
note that changes are made to a temporary state via object.assign (deep copy) and then the new state is returned.
请注意,通过 object.assign(深度复制)对临时状态进行更改,然后返回新状态。
The other answer to the question was a bit confusing. It went into the detail of how to combine different reducers, but it didn't make much sense to me.
这个问题的另一个答案有点令人困惑。它详细介绍了如何组合不同的减速器,但对我来说没有多大意义。
in your reducers/index.ts you should have a type:
在你的 reducers/index.ts 你应该有一个类型:
export interface AppState {
otherReducer: OtherReducer;
offers: fromOffersReducer.OfferState;
}
inside this index.ts, you should have functions that get the reducers:
在此 index.ts 中,您应该具有获取减速器的函数:
export function getOfferState() {
return (state$: Observable<AppState>) => state$
.select(s => s.offers);
}
export function getOtherReducer() {
return (state$ : Observable<AppState>) => state$
.select(s => s.otherReducer)
}
inside our offerReducer and our otherReducer, we define functions that can query the data we need. These are anonymous functions, that are not linked to anything at present, but we will link them later (to the getReducerFunctions).
在我们的 offerReducer 和我们的 otherReducer 中,我们定义了可以查询我们需要的数据的函数。这些是匿名函数,目前没有链接到任何东西,但我们稍后会链接它们(到 getReducerFunctions)。
examples of these functions:
这些函数的例子:
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]);
}
this does nothing. unless we apply it to some useful data (e.g. the offersRedeucer) that we made ealier, and we combine the two like this:
这没有任何作用。除非我们将它应用到我们制作的一些有用的数据(例如 offerReducer),然后我们像这样将两者结合起来:
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());
}
回答by wiredprogrammer
Okay I'll give it a shot at explaining a way to set this up and hopefully do it in a way you and others can understand.
好的,我将尝试解释一种设置方法,并希望以您和其他人可以理解的方式进行。
So if you are storing a array of objects and need to get access to a row by a certain id or key then the way the ngrx example app shows you can do this is to create a object containing your object which will use (in the case of their book app) the book's id as the property key. You can set any string as a property name. So say you have your state with a property called "entities" whose value is a blank object. You can then take your array and create properties on the "entities" Object.
因此,如果您正在存储一组对象并需要通过某个 ID 或键访问一行,那么 ngrx 示例应用程序显示您可以这样做的方式是创建一个包含您将使用的对象的对象(在这种情况下他们的图书应用程序的)图书的 id 作为属性键。您可以将任何字符串设置为属性名称。因此,假设您的状态具有名为“实体”的属性,其值为空白对象。然后,您可以使用您的数组并在“实体”对象上创建属性。
export interface BooksState {
entities: { [id: string]: Book };
};
Lets start with just one row in the array of book objects. To add this row to the "entities" object you do it like this.
让我们从 book 对象数组中的一行开始。要将这一行添加到“实体”对象中,您可以这样做。
reducerState.entities[book.id] = book;
The above will make the book's id a property on the "entities" object.
If you were to inspect it in the console or debug tools it might look something like this after its creation.
以上将使书的 id 成为“实体”对象的一个属性。
如果您要在控制台或调试工具中检查它,它在创建后可能看起来像这样。
reducerState.entities.15: { id: 15, name: "To Kill a Mockingbird" }
The ngrx example app operates on the full array to add it to the state using the reduce operator on the javascript array.
ngrx 示例应用程序对完整数组进行操作,使用 javascript 数组上的 reduce 运算符将其添加到状态中。
const newBookEntities
= newBooks.reduce(
(entities: { [id: string]: Book }, book: Book) =>
{
return Object.assign(entities, { [book.id]: book });
}, {});
And then create special functions to get parts of this state that can be composed together later to get the specific rows you need
然后创建特殊函数来获取此状态的一部分,这些部分可以稍后组合在一起以获得您需要的特定行
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]));
}
And they compose these in the index.ts barrel in the example
他们在示例中的 index.ts 桶中组合这些
import { compose } from '@ngrx/core/compose';
export function getBooks(bookIds: string[]) {
return compose(fromBooks.getBooks(bookIds), getBooksState());
}
This function will chain the calls together going from right to left. First calling getBooksState to get the reducer state and then calling fromBooks.getBooks and passing in the bookid's you want and the state from the previous "getBooksState" and allowing you to do the mapping to the selected states you need.
此函数将从右到左将调用链接在一起。首先调用getBooksState 来获取reducer 状态,然后调用fromBooks.getBooks 并传入您想要的bookid 和之前“getBooksState”的状态,并允许您映射到您需要的选定状态。
In your component you can import this function and use it to get just the books you want.
在您的组件中,您可以导入此函数并使用它来获取您想要的书籍。
myBooks$: Observable<Books> = this.store.let(getBooks(bookIds)));
This returned observable is updated every time the store is updated.
每次更新 store 时,都会更新返回的 observable。
Added:So the operator "let" on the store object passes in the current observable holding the store's state object to the function that is returned by the "getBooks" function.
补充:所以 store 对象上的操作符“let”将保存 store 状态对象的当前 observable 传递给“getBooks”函数返回的函数。
Lets look closer at that.
让我们仔细看看。
export function getBooks(bookIds: string[]) {
return compose(fromBooks.getBooks(bookIds), getBooksState());
}
Two functions are passed into the compose function here -- fromBooks.getBooks(bookIds) and getBooksState(). The compose function allows a value to be passed into the first function on the right and have that functions results passed into the function on the left and so on and so on. So in this case we are going to take the results of the function returned by "getBooksState" and pass it into the function returned by the function "fromBooks.getBooks(bookIds)" and then ultimately return that result.
这里有两个函数被传递到 compose 函数—— fromBooks.getBooks(bookIds) 和 getBooksState()。compose 函数允许将值传递给右侧的第一个函数,并将该函数的结果传递给左侧的函数,依此类推。因此,在这种情况下,我们将获取“getBooksState”返回的函数结果并将其传递给“fromBooks.getBooks(bookIds)”函数返回的函数,然后最终返回该结果。
These two functions take in a Observable. In the case of the function returned by getBooksState() it takes in as a parameter the store state observable object from which it will map/select (map and select are aliases for each other) to the slice of the store we care about and return the new mapped observable. That mapped observable will be passed into the function returned from the fromBooks.getBooks(bookIds) function. This function expects the state slice observable. This function does the new mapping by pulling out only the entities we care about. This new observable is what is ultimately returned by the "store.let" call.
这两个函数接收一个 Observable。在 getBooksState() 返回的函数的情况下,它将商店状态可观察对象作为参数,它将从中映射/选择(映射和选择是彼此的别名)到我们关心的商店切片并返回新映射的 observable。该映射的 observable 将传递到从 fromBooks.getBooks(bookIds) 函数返回的函数中。此函数期望状态切片可观察。这个函数通过只提取我们关心的实体来完成新的映射。这个新的 observable 最终由“store.let”调用返回。
If you need more clarification then please ask questions and I'll do my best to clarify.
如果您需要更多说明,请提出问题,我会尽力澄清。