@proscom/prostore-react-router
TypeScript icon, indicating that this package has built-in type declarations

0.2.13 • Public • Published

prostore-react-router

Адаптер для react-router, связывающий его с prostore.

Основное назначение этого адаптера - упростить подписку компонентов на изменение квери-параметров.

Хранение фильтров и других параметров отображения страницы в квери-параметрах - частый кейс в наших проектах. Это позволяет пользователю например отправить ссылку другу или открыть ссылку в новой вкладке с полным сохранением контекста.

При реализации работы с квери-параметрами напрямую через react-router возникают две проблемы:

  1. Необходимость парсить квери-строку в каждом компоненте и сериализовать значения при записи в url

  2. При изменении одного из квери-параметров будут обновлены все компоненты, подписанные на роутер, даже если они не используют квери-параметр.

Для решения этих проблем и создан этот адаптер. Он предоставляет класс LocationStore, обеспечивающий хранение данных, их сериализацию и десериализацию; React-компонент LocationProvider, отвечающий за синхронизацию react-router и LocationStore; а также хук useLocationQuery, позволяющий подписать компонент на квери-парамтеры.

Использование

Сначала необходимо создать глобальный стор LocationStore и заинжектить его через ProstoreContext:

import { ProstoreContext } from '@proscom/prostore-react';
import { LocationStore, LocationProvider } from '@proscom/prostore-react-router';
import { createBrowserHistory } from 'history';
import { Router } from 'react-router';

// Создаем browserHistory для взаимодействия с браузерной навигацией
const appHistory = createBrowserHistory();

// Ключ по которому мы будем идентифицировать LocationStore в контексте
const STORE_LOCATION = 'location';

// Сериализаторы квери-параметров. Про это см. ниже
const queryTransformers = {};

// Создаем сам стор, передаем ему history и сериализаторы
const locationStore = new LocationStore({ history: appHistory, transformers: queryTransformers });

// Сохраняем все сторы для инжектирования
const stores = {
  [STORE_LOCATION]: locationStore
};

ReactDOM.render(
  // Рендерим контекст react-router с той же самой history, что и LocationStore
  <Router history={appHistory}>
    {/* Инжектим сторы в контекст */}
    <ProstoreContext.Provider value={stores}>
      {/* Рендерим LocationProvider для синхронизации react-router и STORE_LOCATION */}
      <LocationProvider storeOrName={STORE_LOCATION}>
        {/* Наконец, рендерим наше приложение */}
        <App />
      </LocationProvider>
    </ProstoreContext>
  </Router>
);

Теперь можно использовать стор через контекст через useStore. Тогда мы подписываемся на все изменения Location:

import { useStore } from '@proscom/prostore-react';

function MyComponent() {
  const [locationState, locationStore] = useStore(STORE_LOCATION);

  // locationState.location - актуальный Location (как если бы мы вызвали useLocation у react-router)

  // locationState.query - объект вида { [query]: value, ... }, содержащий значения query-параметров

  // locationStore.changeQuery({ key: value, ... }) - позволяет удобно поменять значения одного или нескольких параметров,
  //    в итоге делает history.push с тем же pathname, но другими квери-параметрами
  // Если вызвать locationStore.changeQuery({}, true), то сделает history.replace

  // locationStore.history - appHistory (аналогично вызову useHistory)
}

Можно подписаться только на изменения конкретных квери-параметров через хук useLocationQuery:

import { useLocationQuery } from '@proscom/prostore-react-router';

const QUERY_KEY_PAGE = 'page';
const QUERY_KEY_SEARCH = 'search';

function MyComponent() {
  const [query, locationStore] = useLocationQuery(STORE_LOCATION, [
    // Массив квери-параметров, на которые надо подписаться
    // (если он изменится, то компонент переподпишется на новые параметры)
    QUERY_KEY_PAGE,
    QUERY_KEY_SEARCH
  ]);

  // query.page - значение query-параметра page
  // query.search - значение query-параметра search

  // locationStore - аналогично примеру выше
}

Такой способ подписки позволяет при изменении одного квери-параметра не перерендеривать всю страницу. Обратите внимание, что компонент страницы автоматически перерендеривается при смене location из-за react-router. Поэтому, чтобы оптимизировать этот перерендер, оберните компонент страницы в React.memo:

const IndexPageComponent = React.memo(function IndexPageComponent() {
  // ...

  return (
    <div>
      {/* ... */}
      <MyComponent />
    </div>
  );
});

export function IndexPage() {
  // Не прокидывайте пропы в IndexPageComponent, так как они включают location, и приводят к перерендеру
  return <IndexPageComponent />;
}

За пределами реакта можно подписаться на квери-параметры с помощью метода LocationStore.get$:

// Создает обзервабл на часть квери
const queryPart$ = locationStore.get$(QUERY_KEY_PAGE, QUERY_KEY_SEARCH);
queryPart$.subscribe((queryPart) => {
  /* ... */
});

Текущие значения квери-параметров можно получить через locationStore.state.query, например:

console.log(locationStore.state.query[QUERY_KEY_PAGE]);

Если подписка на квери-парамтеры не требуется, а требуется только получить доступ к функциям стора, то можно использовать хук useContextStore из prostore:

function MyComponent() {
  const locationStore = useContextStore<LocationStore>(STORE_LOCATION);

  return (
    <button onClick={() => locationStore.changeQuery({ enabled: true })}>
      Enable
    </button>
  );
}

Трансформеры

Квери-параметры могут хранить только строки. LocationStore позволяет использовать отдельные трансформеры (сериализаторы и десериализаторы) для различных квери-параметров, чтобы преобразовывать произвольные данные в строки и обратно.

Десериализаторы вызываются только один раз при обновлении url, а сериализаторы - только при вызове LocationStore.changeQuery. Десериализатор также вызывается, если какой-то код подписан на квери-параметр, но он не присутствует в адресной строке. В таком случае queryValue = undefined, и вы можете сами вернуть необходимое значение по-умолчанию. Из сериализатора можно вернуть undefined, чтобы исключить квери-параметр из адресной строки.

Чтобы определить трансформеры, создайте объект интерфейса IQueryTransformers, и передайте его в конструктор LocationStore. В этом объекте ключами являются квери-параметры, а значениями - объекты вида IQueryTransformer.

import {
  IQueryTransformers,
  IQueryTransformer,
  defineQueryTransformers,
  ExtractTransformedQueryParams
} from '@proscom/prostore-react-router';

const QUERY_KEY_PAGE = 'page';

// Создаем трансформер для нужного типа
const numberTransformer: IQueryTransformer<number | undefined> = {
  parse: (queryValue) => {
    const n = +queryValue;
    return isNaN(n) ? undefined : n;
  },
  stringify: (value) =>
    value !== undefined && !isNaN(n) ? String(value) : undefined
};

// Определяем трансформеры через функцию-хелпер, которая проверяет типы
const queryTransformers = defineQueryTransformers({
  [QUERY_KEY_PAGE]: numberTransformer
});

// Тип параметров для использования с useLocationQuery
type TransformedQueryParams = ExtractTransformedQueryParams<
  typeof queryTransformers
>;

const locationStore = new LocationStore({
  transformers: queryTransformers /* ... */
});

// Теперь значение в стейте будет уже десериализованное
// locationStore.state.query[QUERY_KEY_PAGE] = 5

// Значения, отправленные в changeQuery, проходят через сериализацию перед попаданием в url
locationStore.changeQuery({ [QUERY_KEY_PAGE]: 5 });
// Пример использования с useLocationQuery

const queryKeys = [QUERY_KEY_PAGE, QUERY_KEY_SEARCH] as const;

function MyComponent() {
  const [query, locationStore] = useLocationQuery<
    TransformedQueryParams,
    typeof queryKeys
  >(STORE_LOCATION, queryKeys);

  // query.page - значение query-параметра page
  // query.search - значение query-параметра search

  // locationStore - аналогично примеру выше
}

Сохранение квери-параметров

У LocationStore есть метод createUrlPreserver, который позволяет автоматически сохранять квери-параметры в ссылках при переходах между страницами.

function IndexPage() {
  // Для сохранения квери-параметров необходимо подписаться на LocationStore для их получения в десериализованном виде
  const [query, locationStore] = useLocationQuery<LocationStore>(
    STORE_LOCATION,
    [QUERY_KEY_SEARCH]
  );

  // Передайте в функцию те десериализованные квери-параметры, которые необходимо сохранить
  const url = useMemo(() => locationStore.createUrlPreserver(query), [query]);

  // Используйте функцию url для создания ссылки
  return <a href={url('/filters')}>Перейти к фильтрам</a>;
}

// В результате на странице /?search=Test
// компонент отрендерит <a href="/filters?search=Test">Перейти к фильтрам</a>

API

LocationStore

Стор, обеспечивающий синхронизацию react-router и prostore.

history: History

History из пакета history, переданная в конструкторе стора.

state: ILocationStoreState

Состояние стора

state.location: Location

Текущий Location, как в react-router

state.query: ITransformedQuery

Десериализованные значения квери-параметров

get$<Params, Keys extends string[]>(...items: Keys): Observable<QueryPart<Params, Keys>>

Возвращает обзвервабл, содержащий трансформированные значения части квери-параметров

  • items - набор квери параметров, на которые надо подписаться

createUrlPreserver(query: ITransformedQuery|null|undefined)

Создает конструктор ссылок, сохраняющих набор параметров адресной строки.

  • query - набор десериализованных квери-параметров для сохранения. По-умолчанию сохраняет все текущие параметры

Возвращает функцию типа (pathname: string, newQuery?: ITransformedQuery) => string. Она создает ссылку с сохранением query-параметров. При этом прогоняет query-параметры через сериализаторы из набора трансформеров

Параметры функции:

  • pathname - адрес для перехода
  • newQuery - новые квери-параметры

setTransformer(key: string, transformer: IQueryTransformer | undefined)

Позволяет добавить трансформер квери-параметра после создания стора

  • key - квери-параметр
  • transformer - трнасформер

changeQuery(changes: Partial<ITransformedQuery>, replace = false)

Позволяет изменить квери-параметры в URL. Принимает квери-параметры для изменения. Все остальные квери-параметры будут сохранены. Если необходимо убрать какой-то параметр, то в качестве значения следует передать undefined.

По-умолчанию делает history.push (т.е. сохраняет навигацию в истории браузера для кнопки назад).

  • changes - изменения квери-параметров
  • replace - если true, то вместо history.push происходит history.replace

LocationProvider

Компонент, который синхронизирует LocationStore и контекст react-router

useLocationQuery<Params, Keys>(storeOrName: StoreOrName<LocationStore>, keys: Keys)

Хук, позволяющий подписаться на часть квери-параметров

  • storeOrName - LocationStore или его имя в ProstoreContext
  • keys - массив имен квери-параметров, на которые надо подписаться

Возвращает [state, store], где

  • state - трансформированные значения квери-параметров,
  • store - LocationStore

useReactRouter

Инжектирует контекст react-router в компонент

createUrlPreserver(preservedQuery: ITransformedQuery, transformers?: IQueryTransformers)

Создает конструктор ссылок, сохраняющих набор параметров адресной строки.

  • preservedQuery - набор десериализованных квери-параметров для сохранения. Они будут обработаны сериализатором из переданного набора трансформеров
  • transformers - набор трансформеров для сериализации квери-параметров

Возвращает функцию типа (pathname: string, newQuery?: ITransformedQuery) => string. Она создает ссылку с сохранением query-параметров. При этом прогоняет query-параметры через сериализаторы из набора трансформеров

Параметры функции:

  • pathname - адрес для перехода
  • newQuery - новые квери-параметры

IQueryTransformer<T>

Тип для трансформера квери-параметра

defineQueryTransformers<T>(x)

Возвращает переданное значение, но шаманит с типами, чтобы тип результата был наиболее точен, но TS проверял, что он расширяет IQueryTransformers. Используйте эту функцию при создании трансформеров.

ExtractTransformedQueryParams<T>

Тип, достающий из типа трансформеров типы десериализованных значений

Readme

Keywords

none

Package Sidebar

Install

npm i @proscom/prostore-react-router

Weekly Downloads

25

Version

0.2.13

License

ISC

Unpacked Size

106 kB

Total Files

58

Last publish

Collaborators

  • alexeyshilyaev
  • mayorandrew
  • sviryukov
  • a.derbenev