web-dev-qa-db-ja.com

ngrx / storeから単一のアイテムを取得します

私は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プロパティに格納)。

私の問題は、特定の楽器を効率的に検索してストアから取り出す方法がわからないことです。これだけでなく、ストアからのInstrumentがサーバーからのwebsocket Pushを介して頻繁に更新される場合、フェッチするInstrumentも更新する必要があります。そのため、特定の楽器をストアで検索し、Observableとして返す必要があります。これにより、ストアにプッシュされる新しいデータに基づいてViewコンポーネントが更新されます。

どうすればこれを達成できますか?

15
reach4thelasers

取得していないのは、リデューサーで呼び出されるすべてのアクションに対して、新しい状態が返されることです。

質問のサンプルコードから、状態は機器のリストにすぎません。
インデックスがないため、楽器がリストにあるかどうかを確認する唯一の方法は、リスト全体を検索することです。

しかし、もしあなたの州が辞書だったらどうでしょう?さらに、インデックスのリストを辞書とは別に保持するとどうなりますか?

状態タイプは次のとおりです。

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

アクションが実行されるたびに、新しい状態が返されます。状態を直接変更することはできないため、これはReduxの重要な概念です。実際には、レデューサーを作成するときにこれを厳密に実行するのが最善です:(「レデューサーを提供する」と別のレデューサーを持っている場合、それらをコンポーズで1つに結合します。

> 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(ディープコピー)を介して一時的な状態に変更が加えられ、新しい状態が返されることに注意してください。

質問に対する他の答えは少しわかりにくいものでした。異なるレデューサーを組み合わせる方法の詳細に入りましたが、私にはあまり意味がありませんでした。

レデューサー/index.tsには次のタイプが必要です。

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

この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)
}

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

これは何もしません。過去に作成した有用なデータ(offersRedeucerなど)に適用しない限り、この2つを次のように組み合わせます。

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番目のレデューサー)を予想し、「1つのアイテムをどうやって手に入れるの?」という質問に答えようとしました。すぐにその問題に出くわすことになると思います。ただし、元のクエリと混同しないようにしてください。..アイテムを見つける秘trickは、アイテムのマップである状態(辞書)を維持することです。その状態を変更せず、常に新しい状態を返します。そのガイダンスに従ってください、そしてあなたの問題は解決されます。あなたが直面する次の問題は複数の減速機ですが、上記をもう一度読んで、それは理にかなっているはずです。

Stackoverflowの質問に関する以前の回答には同意しません。それらはあなたが必要とするものです。また、チュートリアルが必要な場合は、チュートリアルにすることができます。そして、私はそれが悪いことだとは思わない-特にアドバイスやドキュメンテーションがほとんど入手できないRXJSのようなニッチな主題では。

この質問に賞金をお寄せいただきありがとうございます。より良い回答をお寄せいただければ幸いです。それ以上の質問-ただ叫ぶ。お手伝いさせていただきます。あなたは私から「これ以上あなたの質問に答えるのを嫌がります」とは言わないでしょう...私は助けるためにここにいます-in辱するためではありません。

14
reach4thelasers

さて、これを設定する方法を説明し、できればあなたと他の人が理解できる方法でそれを行うことを試してみましょう。

オブジェクトの配列を保存していて、特定のIDまたはキーで行にアクセスする必要がある場合、ngrxサンプルアプリがこれを行うことができる方法は、使用するオブジェクトを含むオブジェクトを作成することですブックアプリの)プロパティキーとしてのブックのID。プロパティ名として任意の文字列を設定できます。したがって、値が空のオブジェクトである「エンティティ」と呼ばれるプロパティを持つ状態があるとしましょう。その後、配列を取得して、「エンティティ」オブジェクトのプロパティを作成できます。

export interface BooksState {
  entities: { [id: string]: Book };
};

ブックオブジェクトの配列の1行だけから始めましょう。この行を「エンティティ」オブジェクトに追加するには、次のようにします。

 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を呼び出してリデューサーの状態を取得し、次にfromBooks.getBooksを呼び出して、必要なブックIDと前の「getBooksState」の状態を渡し、必要な選択状態へのマッピングを可能にします。

コンポーネントでこの関数をインポートし、それを使用して必要な書籍だけを取得できます。

myBooks$: Observable<Books>  =  this.store.let(getBooks(bookIds)));

この返されるオブザーバブルは、ストアが更新されるたびに更新されます。

追加:したがって、ストアオブジェクトの演算子「let」は、ストアの状態オブジェクトを保持する現在のオブザーバブルを「getBooks」によって返される関数に渡します。 " 関数。

それを詳しく見てみましょう。

export function getBooks(bookIds: string[]) {
   return compose(fromBooks.getBooks(bookIds), getBooksState());
}

ここでは、fromBooks.getBooks(bookIds)とgetBooksState()の2つの関数が構成関数に渡されます。 compose関数を使用すると、右側の最初の関数に値を渡すことができ、その関数の結果を左側の関数に渡すことができます。したがって、この場合、「getBooksState」によって返された関数の結果を取得し、「fromBooks.getBooks(bookIds)」関数によって返された関数に渡し、最終的にその結果を返します。

これらの2つの関数はObservableを取り込みます。 getBooksState()によって返される関数の場合、パラメータとして、それが関心のあるストアのスライスにマップ/選択(マップと選択は相互のエイリアス)するストア状態監視可能オブジェクトを受け取りますマッピングされた新しいオブザーバブル。マッピングされたオブザーバブルは、fromBooks.getBooks(bookIds)関数から返される関数に渡されます。この関数は、状態スライスが観測可能であることを期待しています。この関数は、関心のあるエンティティのみを引き出すことで新しいマッピングを行います。この新しいオブザーバブルは、「store.let」呼び出しによって最終的に返されるものです。

さらに説明が必要な場合は、質問してください。明確にするために最善を尽くします。

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

8
wiredprogrammer