получить отдельный элемент из ngrx/store

Я написал следующий редуктор для хранения элементов состояния в своем приложении 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 является чем-то вроде парадигмы, от меня от того, как я кодировался годами.

У меня есть компонент Instrument/Page на моем приложении - который показывает всю доступную информацию для одного конкретного инструмента, обозначенного InstrumentId, например, "EUR/USD" (хранится в свойстве полезной нагрузки.Инструмент).

Моя проблема в том, что я не уверен, как эффективно искать конкретный инструмент и вытащить его из магазина. Не только это, но я также хочу, чтобы инструмент, который я получал, обновлялся, если Инструмент в магазине обновлен, поскольку они часто проходят через веб-сервер с сервера. Поэтому мне действительно нужно искать в магазине конкретный инструмент и возвращать его как Observable, который будет продолжать обновлять компонент View на основе новых данных, которые попадают в хранилище.

Как я могу это достичь?

Ответ 1

То, что вы не получаете, - это то, что для каждого действия, вызываемого редуктором, возвращается новое состояние.

Из вашего примера кода в вопросе ваше состояние - это всего лишь список инструментов.
Там нет индекса, поэтому единственный способ проверить, находится ли инструмент в списке, - это поиск по всему списку.

Но что, если ваше государство было в словаре? Кроме того, что, если вы сохранили список индексов, разделяющих словарь?

ваш тип состояния таков:

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

Каждый раз, когда действие выполняется, возвращается новое состояние. Это важная концепция в Redux, потому что государство никогда не может быть мутировано напрямую. На самом деле вы лучше всего это соблюдаете, когда составляете свой редуктор: (скажем, у вас есть "предложение редуктора" и еще один редуктор, вы объединяете их с одним, чтобы составить:

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

внутри нашего предложенияReducer и нашего другогоReducer мы определяем функции, которые могут запрашивать нужные нам данные. Это анонимные функции, которые в настоящее время не связаны ни с чем, но мы свяжем их позже (с 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, где мало советов и документации.

Спасибо, что поставил щедрость на этот вопрос - надеюсь, что я дал вам лучший ответ. Любые дополнительные вопросы - просто кричите. Я рад помочь. Вы не получите от меня "я не хочу отвечать на ваши вопросы"... Я здесь, чтобы помочь, а не оскорблять.

Ответ 2

Хорошо, я дам ему шанс объяснить, как это сделать, и, надеюсь, сделать это так, как вы и другие понимаете.

Поэтому, если вы храните массив объектов и вам нужно получить доступ к строке определенным идентификатором или ключом, то, как показывает пример приложения ngrx, вы можете создать объект, содержащий ваш объект, который будет использовать (в случае их книжного приложения) идентификатор книги как ключ собственности. Вы можете указать любую строку как имя свойства. Поэтому скажите, что у вас есть ваше состояние с свойством, называемым "сущностями", значение которого является пустым объектом. Затем вы можете взять свой массив и создать свойства объекта "сущности".

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

Давайте начнем с одной строки в массиве объектов книги. Чтобы добавить эту строку в объект "entity", вы делаете это так.

 reducerState.entities[book.id] = book;

Вышеизложенное делает идентификатор книги свойством объекта "сущности".
Если вы проверили его в консоли или инструменты отладки, это может выглядеть примерно так после его создания.

 reducerState.entities.15: { id: 15, name: "To Kill a Mockingbird" }

Приложение примера ngrx работает с полным массивом, чтобы добавить его в состояние, используя оператор сокращения в массиве javascript.

   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 и передавать требуемую книгу и состояние из предыдущего "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(). Функция компоновки позволяет передать значение в первую функцию справа и передать результаты функций в функцию слева и т.д. И т.д. Поэтому в этом случае мы собираемся взять результаты функции, возвращаемой "getBooksState", и передать ее в функцию, возвращаемую функцией "fromBooks.getBooks(bookIds)", а затем в конечном итоге вернуть этот результат.

Эти две функции занимают Наблюдаемое. В случае функции, возвращаемой getBooksState(), она принимает в качестве параметра объект наблюдаемого состояния наблюдения, из которого он будет отображать/выбирать (отображать и выбирать псевдонимы друг для друга) на кусочек магазина, который мы заботимся и возвращаем новый отображаемый наблюдаемый. Отображаемое отображение будет передано в функцию, возвращаемую функцией fromBooks.getBooks(bookIds). Эта функция ожидает наблюдаемый срез состояния. Эта функция выполняет новое отображение, вытаскивая только те объекты, которые нам интересны. Это новое наблюдаемое - это то, что в конечном итоге возвращается вызовом "store.let".

Если вам нужно больше разъяснений, задайте вопросы, и я сделаю все возможное, чтобы уточнить.

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