@proscom/prostore-react-router
v0.2.13
Published
Adapter for react-router and prostore. Allows subscription to query parameters and more
Downloads
23
Keywords
Readme
prostore-react-router
Адаптер для react-router
, связывающий его с prostore
.
Основное назначение этого адаптера - упростить подписку компонентов на изменение квери-параметров.
Хранение фильтров и других параметров отображения страницы в квери-параметрах - частый кейс в наших проектах. Это позволяет пользователю например отправить ссылку другу или открыть ссылку в новой вкладке с полным сохранением контекста.
При реализации работы с квери-параметрами напрямую через react-router
возникают две проблемы:
Необходимость парсить квери-строку в каждом компоненте и сериализовать значения при записи в url
При изменении одного из квери-параметров будут обновлены все компоненты, подписанные на роутер, даже если они не используют квери-параметр.
Для решения этих проблем и создан этот адаптер.
Он предоставляет класс 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 или его имя в ProstoreContextkeys
- массив имен квери-параметров, на которые надо подписаться
Возвращает [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>
Тип, достающий из типа трансформеров типы десериализованных значений