И на клиенте и на сервере
мы используем StateManager например Redux
там и там создаём соответствующий Store
на сервере нам необходимо
отрендерить соотвествующую страницу
вызываем функцию renderHtmlPage
для того чтобы работать правильно с путями
переходить с одной страницы на другую
нам необходим роутер Router
роутер нужен как на клиенте, так и на сервере
причем работать будут по разному
- на сервере роутер нужен
правильно распарсить путь, определить страницу, которую необходимо отрендерить и после чего вернуть её уже на клиент - на клиенте роутер нужен
для перемещения с одной страницы на другую
const renderHtmlPage = (store: any, url: any) => {
const router = new Router();
}
const client = () => {
const store = createStore(initialData);
const router = new Router();
}
const server = () => {
const store = createStroe(initialData);
const htmlPage = renderHtmlPage(stroe, req.url)
}
общий интерфейс для клиенского и серверного роутера
- создаём класс Router и имплементируем туда интерфейс
- создаём один единственный метод
который предназначен для парсить url parseUrl
он может использоваться как на клиенте, так и на сервере
interface IRouter {
parseUrl: (url) => void
}
class Router implements IRouter {
parseUrl(url): void {}
}
в интерфейсе роутер IRouter
Добавляем метод navigate
это перемещение с одной страницы на другую
использоваться он будет только на клиенте client
enum Route {
ABOUT='about_page',
HOME='home_page',
}
interface IRouter {
parseUrl: (url) => void
navigate: (route: Route) => void
}
class Router implements IRouter {
parseUrl(url): void {}
navigate(route: Route): void {}
}
в интерфейсе роутер IRouter
Добавляем ещё один метод attachEventListeners
позволяет привязать, слушать события
использоваться он будет только на клиенте client
enum Route {
ABOUT='about_page',
HOME='home_page',
}
interface IRouter {
parseUrl: (url) => void
navigate: (route: Route) => void
attachEventListeners: () => void
}
class Router implements IRouter {
parseUrl(url): void {}
navigate(route: Route): void {}
attachEventListeners: () => void {}
}
в интерфейсе роутер IRouter
Добавляем ещё один метод addQueryParams
позволяет в строку запроса добавить какие-то record параметры
использоваться он будет как на клиенте client так и на server
enum Route {
ABOUT='about_page',
HOME='home_page',
}
interface IRouter {
parseUrl: (url) => void
navigate: (route: Route) => void
attachEventListeners: () => void
addQueryParams: (params: Record<string, string>) => void
}
class Router implements IRouter {
parseUrl(url): void {}
navigate(route: Route): void {}
attachEventListeners: () => void {}
addQueryParams: (params: Record<string, string>) => void {}
}
Мы получили один обобщенный класс
который содержит лишние методы
как для клиента, так и для сервера
1 Способ
Расширение интерфейсов
- Создаём общий интерфейс Router
отвечает только за parserUrl и addQuerParams - отдельный интерфейс для клиенского роутера ClientRouter
наследует от Router и отвечает только за
навигацию navigate и attachEventListeners - отдельный интерфейс для серверного роутера ServerRouter
наследует от Router и отвечает только за
навигацию prepareUrlForClient
interface IRouter {
parseUrl: (url) => void
addQueryParams: (params: Record<string, string>) => void
}
interface ClientRouter extends IRouter {
navigate: (route: Route) => void
attachEventListeners: () => void
}
interface ServerRouter extends IRouter {
prepareUrlForClient: (url: string) => void
}
В соответствующие классы мы
имплементируем серверный роутер и клиенский роутер
реализуем в каждом классе нужные методы
class ServerRouter implements IServerRouter {
parseUrl(url): void {}
addQueryParams: (params: Record<string, string>): void {}
prepareUrlForClient: (url: string): void {}
}
class ClientRouter implements IClientRouter {
parseUrl(url): void {}
addQueryParams: (params: Record<string, string>): void {}
navigate: (route: Route): void {}
attachEventListeners: (): void {}
}
- В серверной части приложения
мы создаём серверный роутер ServerRouter - В клиенской части приложения
мы создаём клиенский роутер ClientRouter
const renderHtmlPage = (store: any, url: any) => {
const router = new ServerRouter();
}
const client = () => {
const store = createStore(initialData);
const router = new ClientRouter();
}
const server = () => {
const store = createStroe(initialData);
const htmlPage = renderHtmlPage(stroe, req.url)
}
Мы могли создать два разных класса и создать там методы
Но по хорошему чтобы эти два роутера
- либо наследовались от какого-то одного базового класса
- либо имплементировали какой-то общий интерфейс для того чтобы мы могли работать с этими роутарами независимо от того находимся мы в серверной части, либо на клиенской
как на клиенте, так и на сервере этот код одинаков
const createDependencyContainer = (router: Router, store) => {
return {
getRouter: () => router,
getStore: () => store,
}
}
2 Способ
Без расширения интерфейсов
отдельные интерфейсы
- Создать отдельный интерфейс роутер Router
- Создать отдельный интерфейс для парсинга и преобразования
- Создать отдельный интерфейс для url подготовки для клиента
interface UrlParser {
parseUrl: (url) => void
addQueryParams: (params: Record<string, string>) => void
}
interface Router {
navigate: (route: Route) => void
attachEventListeners: () => void
}
interface UrlPreparer extends IRouter {
prepareUrlForClient: (url: string) => void
}
В нужные нам классы имплементировать нам нужные интерфейсы и реализовать методы
class ServerRouter implements UrlParser, UrlPreparer {
parseUrl(url): void {}
addQueryParams: (params: Record<string, string>): void {}
prepareUrlForClient: (url: string): void {}
}
class ClientRouter implements Router, UrlParser{
parseUrl(url): void {}
addQueryParams: (params: Record<string, string>): void {}
navigate: (route: Route): void {}
attachEventListeners: (): void {}
}