16

我编写了以下减速器来将状态项存储在我的 Angular 2 应用程序中。这些项目是金融工具(例如股票/货币)的报价。

我的减速器实现如下:

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 指示,例如“EUR/USD”(存储在 payload.Instrument 属性中)。

我的问题是,我不确定如何有效地搜索特定乐器并将其从商店中抢走。不仅如此,如果商店中的仪器更新,我还希望更新我获取的仪器,因为它们经常通过服务器的 websocket 推送。所以我真的需要在商店中搜索特定仪器,并将其作为 Observable 返回,它将根据推送到商店的新数据继续更新视图组件。

我怎样才能做到这一点?

4

2 回答 2

14

对于在 reducer 上调用的每个操作,都会返回新状态。

从问题中的示例代码中,状态只是仪器列表。
没有索引,因此检查工具是否在列表中的唯一方法是搜索整个列表。

但是如果你的州是一本字典呢?此外,如果您将索引列表与字典分开会怎样?

你的状态类型是这样的:

export interface OfferState {
  ids: string[];
  entities: { [id: string]: IOffer };
};

任何时候执行一个动作,都会返回新的状态。这是 Redux 中的一个重要概念,因为状态永远不能直接改变。实际上,当您编写减速器时,您最好严格执行此操作:(假设您有“提供减速器”和另一个减速器,您可以将它们与 compose 结合为一个:

> export default compose(storeFreeze, combineReducers) ({   oether:
> otherReducer,   offers: offersReducer });

在 Redux 中很容易做错事——但是如果你尝试直接改变状态,使用 storeFreeze 会抛出一个错误。关键是动作会改变状态,并产生新的状态。他们不会改变现有的状态——它让我们撤消/重做……等等。

使用上面的示例,我将使用它作为我的提议的减速器:

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(深拷贝)对临时状态进行更改,然后返回新状态。

这个问题的另一个答案有点令人困惑。它详细介绍了如何组合不同的减速器,但对我来说没有多大意义。

在你的 reducers/index.ts 你应该有一个类型:

export interface AppState {
  otherReducer: OtherReducer;
  offers: fromOffersReducer.OfferState;
}

在这个 index.ts 中,你应该有获取 reducer 的函数:

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 和 otherReducer 中,我们定义了可以查询我们需要的数据的函数。这些是匿名函数,目前没有链接到任何东西,但我们稍后会链接它们(到 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]);
}

这没有任何作用。除非我们将它应用到我们之前制作的一些有用的数据(例如,offersReducer),然后我们像这样将两者结合起来:

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());
 }
于 2016-08-30T23:42:26.333 回答
8

好的,我将尝试解释一种设置方法,并希望以您和其他人可以理解的方式进行。

因此,如果您要存储一个对象数组并且需要通过某个 id 或键访问一行,那么 ngrx 示例应用程序显示您可以执行此操作的方式是创建一个包含您将使用的对象的对象(在这种情况下他们的图书应用程序的)图书的 id 作为属性键。您可以将任何字符串设置为属性名称。因此,假设您的状态具有名为“实体”的属性,其值为空白对象。然后,您可以获取您的数组并在“实体”对象上创建属性。

export interface BooksState {
  entities: { [id: string]: 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”将持有商店状态对象的当前可观察对象传递给“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”调用返回。

如果您需要更多澄清,请提出问题,我会尽力澄清。

https://github.com/ngrx/example-app

于 2016-08-23T15:37:48.970 回答