【问题标题】:get single item from ngrx/store从 ngrx/store 获取单个项目
【发布时间】:2016-12-21 19:15:09
【问题描述】:

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

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

我怎样才能做到这一点?

【问题讨论】:

  • 我在第一次开始使用 redux/ngrxStore 时遇到了许多相同的问题。我从 ngrx 示例应用程序中获得了很大的灵感。 github.com/ngrx/example-app 如果您查看他们构成状态和检索值的方式,它会有所帮助。使用 Redux,鼓励您将状态标准化为类似于数据库表。我的第二个障碍是学习如何有效地使用 observables 和 rxjs。如果做得好,您可以真正简化您的代码并使其非常易读(假设您了解 rxjs 运算符)并且您的应用程序非常快速且可测试。
  • 这些移植到使用 ngrx/store 的 react/redux 示例也可能有所帮助。 github.com/btroncone/ngrx-examples
  • 它们本质上是针对状态构建索引。 const newBookEntities = newBooks.reduce((entities: { [id: string]: Book }, book: Book) => { return Object.assign(entities, { [book.id]: book }); }, {}) ;
  • 然后使用该id直接获取对象。 state$ .select(s => s.entities[id]);
  • 这有帮助吗?

标签: angular typescript redux store ngrx


【解决方案1】:

对于在 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]);
}

这没有任何作用。除非我们将它应用到我们之前制作的一些有用数据(例如 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());
 }

【讨论】:

  • 我尽我所能深入回答这个问题。我感到有点被轻视了,不幸的是,这阻碍了我参与这个论坛。我尊重这里有一个意见方面,但尽管我想提供帮助,解释事情有助于我更好地理解这个主题,但这并不是一次积极的经历。
  • 更详细?也许他能够在你没有更好理解的部分上磨练出来,但他重申了很多我已经写过的内容。
  • 这最终无关紧要。我很高兴你得到了你想要的答案。我真的不明白他告诉你的我没有,但就像我说的那样,标记答案有一个意见方面,我会尊重这一点。
  • 但你回答了自己的问题
  • 不,我绝对没有。这个问题有赏金,我不能把答案奖励给自己……我不知道为什么我被列为回答者。
【解决方案2】:

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

因此,如果您要存储一个对象数组并且需要通过某个 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)));

每次更新 store 时都会更新这个返回的 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

【讨论】:

  • 感谢您的详细回答。你能解释一下这个语法吗:entities: { [id: string]: Book };我不确定那是在表达什么。什么是 JS 等价物?
  • 那是 TypeScript 的类型声明。它只是说实体对象应该具有由字符串组成的属性名称,其值是书籍对象或符合书籍对象接口。 JavaScript 中没有等价物,在 TypeScript 中编译时,这不会延续到 JavaScript。它仅用于检查 TypeScript 代码以确保传入正确的类型(在本例中为函数)。
  • 好的,我到了!!!几个问题。 let 在 this.store.let(getBooks()) 中做了什么?以及用于获取状态返回 observables 的特殊函数。我如何才能立即获取状态,而不是等待更改发布到 Observable?
  • store 对象本质上是一个 BehaviorSubject(它是 RXJS 的一部分),所以您所要做的就是订阅它以获取当前值。 store 上的 "let" 相当于 rxjs 的 "let" 运算符。 github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/… "let" 运算符允许您处理原始的 observable。 main store 本质上只是一个普通的 javascript 对象,它被包裹在一个 observable 中。所以在这种情况下,它将对 store 的 observable 的引用传递给函数 getBooks 返回的函数。
  • 如果您查看 getBooks,它会返回一个以 state$ Observable 作为参数的函数。导出函数 getBooks(bookIds: string[]) { return (state$: Observable) => state$ .let(getBookEntities()) .map(entities => bookIds.map(id => entities[id]) ); }
猜你喜欢
  • 2017-08-10
  • 2018-03-30
  • 2022-04-29
  • 1970-01-01
  • 2017-06-13
  • 2017-05-04
  • 1970-01-01
  • 2021-12-07
  • 2019-11-20
相关资源
最近更新 更多