{"id":465891,"date":"2025-07-03T15:06:15","date_gmt":"2025-07-03T15:06:15","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=465891"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=465891","title":{"rendered":"<span>\u041d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044f \u0431\u0435\u0437 \u0445\u0430\u043e\u0441\u0430: \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432 \u0432 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u0443\u0435\u043c\u043e\u043c TypeScript-\u043f\u0440\u043e\u0435\u043a\u0442\u0435<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u041f\u0440\u0438\u0432\u0435\u0442! \u042f \u0418\u043b\u044c\u044f, \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434-\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0432 \u0444\u0438\u043d\u0442\u0435\u0445-\u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438 \u0422\u043e\u0447\u043a\u0430. \u041d\u0430\u043c \u0432\u0430\u0436\u043d\u043e, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0431\u044b\u043b\u0430 \u043d\u0430 \u0432\u044b\u0441\u043e\u043a\u043e\u043c \u0443\u0440\u043e\u0432\u043d\u0435, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0434\u0435\u0441\u044f\u0442\u043a\u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0434\u043b\u044f \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u043e\u0431\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u0438\u0441\u0442\u043e\u0432 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438. \u042f \u0440\u0430\u0431\u043e\u0442\u0430\u044e \u043d\u0430\u0434 \u043e\u0434\u043d\u0438\u043c \u0438\u0437 \u0442\u0430\u043a\u0438\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432. \u041e\u043d \u0430\u043a\u0442\u0438\u0432\u043d\u043e \u0440\u0430\u0437\u0432\u0438\u0432\u0430\u0435\u0442\u0441\u044f: \u0435\u0436\u0435\u043c\u0435\u0441\u044f\u0447\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0431\u043e\u043b\u0435\u0435 10 \u043d\u043e\u0432\u044b\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u2014 \u0441\u0435\u0439\u0447\u0430\u0441 \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0438\u0445 \u0431\u043e\u043b\u044c\u0448\u0435 120.<\/p>\n<p>\u0412 \u0441\u0442\u0430\u0442\u044c\u0435 \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443, \u043a\u0430\u043a \u043c\u044b \u043f\u043e\u044d\u0442\u0430\u043f\u043d\u043e \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u0430\u043b\u0438 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u043f\u0443\u0442\u0435\u0439 \u0440\u043e\u0443\u0442\u0435\u0440\u0430 \u0438 \u0441\u0432\u044f\u0437\u0430\u043b\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u0441 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430\u043c\u0438 \u0438\u0445 \u0432\u0451\u0440\u0441\u0442\u043a\u0438. \u0422\u0430\u043a\u043e\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442 \u043f\u043e\u0432\u044b\u0441\u0438\u0442\u044c \u0447\u0438\u0442\u0430\u0435\u043c\u043e\u0441\u0442\u044c \u043a\u043e\u0434\u0430, \u0441\u043e\u043a\u0440\u0430\u0449\u0430\u0435\u0442 \u0435\u0433\u043e \u0434\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438 \u0443\u043f\u0440\u043e\u0449\u0430\u0435\u0442 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443.<\/p>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440\u044b \u0432 \u0441\u0442\u0430\u0442\u044c\u0435 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u044b \u043d\u0430 TypeScript, React \u0438 React Router, \u043d\u043e \u043f\u043e\u0434\u0445\u043e\u0434 \u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u0435\u043d \u0438 \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0442\u044c\u0441\u044f \u0432 \u0434\u0440\u0443\u0433\u0438\u0445 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044f\u0445:<\/p>\n<ul>\n<li>\n<p>React, Next.js, Vue, Svelte (Frontend).<\/p>\n<\/li>\n<li>\n<p>React Native (Mobile).<\/p>\n<\/li>\n<li>\n<p>Electron (Desktop).<\/p>\n<\/li>\n<li>\n<p>Node.js, NestJS, Express (Backend).<\/p>\n<\/li>\n<\/ul>\n<h2>\u0427\u0430\u0441\u0442\u0430\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u0443\u0442\u0435\u0439 \u0440\u043e\u0443\u0442\u0438\u043d\u0433\u0430<\/h2>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/d36\/b8a\/543\/d36b8a5436f34251f01f6955a4a18377.png\" width=\"1376\" height=\"984\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/d36\/b8a\/543\/d36b8a5436f34251f01f6955a4a18377.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/d36\/b8a\/543\/d36b8a5436f34251f01f6955a4a18377.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<pre><code class=\"typescript\">export const MAIN_PAGE_PATH = '\/'; export const AUTH_PAGE_PATH = '\/login'; export const NOT_FOUND_PAGE_PATH = '\/404'; export const SEARCH_PAGE_PATH = '\/search'; export const TASKS_PAGE_PATH = '\/tasks'; export const ACTUAL_TASKS_PAGE_PATH = '\/tasks\/actual'; export const FINISHED_TASKS_PAGE_PATH = '\/tasks\/finished'; export const getTaskPagePath = (id: number, type: string): string =&gt; `\/tasks\/${id}?type=${type}`; \/\/ \u0438 \u044d\u0442\u043e \u0445\u043e\u0440\u043e\u0448\u043e \u0435\u0449\u0451, \u0435\u0441\u043b\u0438 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0432\u044b\u043d\u0435\u0441\u0435\u043d \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u043e \/\/ ...\u0438 \u0437\u0434\u0435\u0441\u044c \u0435\u0449\u0451 \u0441\u0442\u043e \u0448\u0442\u0443\u043a<\/code><\/pre>\n<p>\u041e\u0431\u044b\u0447\u043d\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445 \u0432\u044b\u0433\u043b\u044f\u0434\u044f\u0442 \u0438\u043c\u0435\u043d\u043d\u043e \u0442\u0430\u043a.<\/p>\n<p>\u042d\u0442\u0438 \u043c\u043d\u043e\u0433\u043e\u0447\u0438\u0441\u043b\u0435\u043d\u043d\u044b\u0435 \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u044b \u043f\u0440\u0438 \u0432\u0451\u0440\u0441\u0442\u043a\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u0432 \u0442\u0430\u043a\u043e\u043c \u0444\u043e\u0440\u043c\u0430\u0442\u0435:<\/p>\n<pre><code class=\"typescript\">--------- home-page.tsx: import { useNavigate } from 'react-router-dom'; \/\/ \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0443\u0442\u0438\u043b\u0438\u0442\u044b \u0434\u043b\u044f \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0438 const navigate = useNavigate(); \/\/ \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0435\u043c \u043f\u0435\u0440\u0435\u0445\u043e\u0434 \u043d\u0430 \u0434\u0440\u0443\u0433\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443, \u043f\u0440\u043e\u043a\u0438\u0434\u044b\u0432\u0430\u0435\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b navigate(getTaskPagePath(task.id, task.type))  --------- task-page.tsx: import { useParams } from 'react-router'; import { useQueryParams } from 'shared\/helpers'; \/\/ \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b const { id } = useParams&lt;{ id: number }&gt;(); const { type } = useQueryParams&lt;{ type: string }&gt;(); \/\/ \u043a\u0430\u043a-\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c id \u0438 type \u0432 \u0432\u0435\u0440\u0441\u0442\u043a\u0435 ...<\/code><\/pre>\n<p>\u041f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c \u0438 \u0440\u0430\u0437\u0432\u0438\u0432\u0430\u0442\u044c \u0442\u0430\u043a\u043e\u0439 \u043a\u043e\u0434 \u043d\u0435\u043f\u0440\u043e\u0441\u0442\u043e. \u0421\u0440\u0435\u0434\u0438 \u043e\u0447\u0435\u0432\u0438\u0434\u043d\u044b\u0445 \u043f\u0440\u043e\u0431\u043b\u0435\u043c:<\/p>\n<ol>\n<li>\n<p><strong>\u041f\u043e\u0438\u0441\u043a \u043d\u0443\u0436\u043d\u043e\u0433\u043e \u043f\u0443\u0442\u0438 \u0437\u0430\u0442\u0440\u0443\u0434\u043d\u0451\u043d \u043e\u0434\u043d\u043e\u0442\u0438\u043f\u043d\u043e\u0441\u0442\u044c\u044e \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442.<\/strong> \u041f\u0440\u0438 \u0431\u043e\u043b\u044c\u0448\u043e\u043c \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u043e\u0445\u043e\u0436\u0438\u0445 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043b\u0435\u0433\u043a\u043e \u0437\u0430\u043f\u0443\u0442\u0430\u0442\u044c\u0441\u044f \u0438 \u043f\u043e\u0442\u0440\u0430\u0442\u0438\u0442\u044c \u043b\u0438\u0448\u043d\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u043d\u0430 \u043f\u043e\u0438\u0441\u043a \u043d\u0443\u0436\u043d\u043e\u0439.<\/p>\n<\/li>\n<li>\n<p><strong>\u0414\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0432 \u043d\u0435\u0439\u043c\u0438\u043d\u0433\u0435<\/strong>. \u0412 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u043c\u043e\u0433\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u044b \u0441 \u0441\u0443\u0444\u0444\u0438\u043a\u0441\u0430\u043c\u0438 PAGE, PATH, ROUTE \u0438 \u0442.\u043f. \u042d\u0442\u043e \u0443\u0441\u043b\u043e\u0436\u043d\u044f\u0435\u0442 \u0447\u0442\u0435\u043d\u0438\u0435 \u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u043a\u043e\u0434\u0430.<\/p>\n<\/li>\n<li>\n<p><strong>\u041e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u0441\u0432\u044f\u0437\u0438 \u043c\u0435\u0436\u0434\u0443 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438 \u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u043c\u0438<\/strong>. Path- \u0438 query-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u044b \u0438 \u0432 \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u0430\u0445 \u043f\u0443\u0442\u0435\u0439, \u0438 \u0432 \u0441\u0430\u043c\u0438\u0445 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446. \u0412 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u0434\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0442\u0438\u043f\u043e\u0432 \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0432\u0435\u0440\u043e\u044f\u0442\u043d\u043e\u0441\u0442\u044c \u043d\u0435\u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0439 \u0438 \u043e\u0448\u0438\u0431\u043e\u043a.<\/p>\n<\/li>\n<\/ol>\n<p>\u041a\u043e\u0433\u0434\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432 \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u0431\u043e\u043b\u044c\u0448\u0435, \u044d\u0442\u0438 \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043d\u0430\u043a\u0430\u043f\u043b\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0438 \u043d\u0435\u0433\u0430\u0442\u0438\u0432\u043d\u043e \u0432\u043b\u0438\u044f\u044e\u0442 \u043d\u0430 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u0443\u0435\u043c\u043e\u0441\u0442\u044c.<\/p>\n<h2>\u0411\u0430\u0437\u043e\u0432\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b<\/h2>\n<p>\u0412 \u0441\u0432\u043e\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u043c\u044b \u0440\u0435\u0448\u0438\u043b\u0438 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0435\u0434\u0438\u043d\u044b\u0439 \u043e\u0431\u044a\u0435\u043a\u0442, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043a\u043b\u044e\u0447\u0438 \u0438\u043c\u0435\u044e\u0442 \u043f\u043e\u043b\u043d\u043e\u0435 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435 \u0441\u0442\u0440\u043e\u043a\u043e\u0432\u043e\u043c\u0443 \u043f\u0443\u0442\u0438, \u043a\u0440\u043e\u043c\u0435 \u0434\u0432\u0443\u0445 \u0441\u043b\u0443\u0436\u0435\u0431\u043d\u044b\u0445:<\/p>\n<ul>\n<li>\n<p><strong>index<\/strong> \u0434\u043b\u044f \u043a\u043e\u0440\u043d\u0435\u0432\u044b\u0445 \u043f\u0443\u0442\u0435\u0439;<\/p>\n<\/li>\n<li>\n<p><strong>item<\/strong> \u0434\u043b\u044f \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u043f\u0443\u0442\u0435\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432, \u043e\u0431\u044b\u0447\u043d\u043e \u043f\u043e \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0430\u043c.<\/p>\n<\/li>\n<\/ul>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0432\u0441\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u0432 \u0442\u0430\u043a\u043e\u043c \u0432\u0438\u0434\u0435:<\/p>\n<pre><code class=\"typescript\">export const Routes = {   index: '\/',   login: '\/login',   notFound: '\/404',   search: '\/search',   tasks: {     index: '\/tasks',     actual: '\/tasks\/actual',     finished: '\/tasks\/finished',     item: (id: number, type: string) =&gt; `\/tasks\/${id}?type=${type}`   }, }<\/code><\/pre>\n<p>\u0410 \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044f \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"typescript\">const navigate = useNavigate(); ... navigate(Routes.tasks.item(123))<\/code><\/pre>\n<p><strong>\u042d\u0442\u043e \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u043e\u0435 \u0438 \u0431\u044b\u0441\u0442\u0440\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435, \u0438 \u0442\u0435\u043f\u0435\u0440\u044c \u0443 \u043d\u0430\u0441<\/strong>:<\/p>\n<ul>\n<li>\n<p>\u0435\u0434\u0438\u043d\u0430\u044f \u0442\u043e\u0447\u043a\u0430 \u0432\u044b\u0431\u043e\u0440\u0430 \u043f\u0443\u0442\u0438, \u0430 \u0430\u0432\u0442\u043e\u043a\u043e\u043c\u043f\u043b\u0438\u0442 IDE \u0431\u044b\u0441\u0442\u0440\u043e \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u043d\u0430\u0439\u0442\u0438 \u043d\u0443\u0436\u043d\u044b\u0439;<\/p>\n<\/li>\n<li>\n<p>\u0432\u0441\u0435 \u043f\u0443\u0442\u0438 \u0447\u0451\u0442\u043a\u043e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u044b, \u043b\u0435\u0433\u043a\u043e \u043e\u0440\u0438\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u043d\u043e\u0432\u044b\u0435;<\/p>\n<\/li>\n<li>\n<p>\u043d\u0435\u0442 \u0434\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043b\u0438\u0448\u043d\u0438\u0445 \u0441\u043b\u043e\u0432 \u0432 \u043d\u0435\u0439\u043c\u0438\u043d\u0433\u0435 \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442.<\/p>\n<\/li>\n<\/ul>\n<p>\u041d\u0435 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u044c \u0438\u043c\u0435\u043d\u043d\u043e \u043d\u0430\u0448\u0435\u0439 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0435: \u0441\u043e\u0431\u043b\u044e\u0434\u0430\u0442\u044c \u0442\u043e\u0447\u043d\u044b\u0439 \u043d\u0435\u0439\u043c\u0438\u043d\u0433 \u043a\u043b\u044e\u0447\u0435\u0439, \u044e\u0437\u0430\u0442\u044c \u0441\u043b\u0443\u0436\u0435\u0431\u043d\u044b\u0435 index\/item \u0438\u043b\u0438 \u0435\u0434\u0438\u043d\u044b\u0439 \u043e\u0431\u044a\u0435\u043a\u0442 \u2014 \u044d\u0442\u043e \u043b\u0438\u0448\u044c \u0434\u043e\u0433\u043e\u0432\u043e\u0440\u0451\u043d\u043d\u043e\u0441\u0442\u044c \u043d\u0430\u0448\u0438\u0445 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432. \u0413\u043b\u0430\u0432\u043d\u043e\u0435 \u2014 \u0432\u044b\u0431\u0440\u0430\u0442\u044c \u0443\u0434\u043e\u0431\u043d\u0443\u044e \u0438 \u043f\u043e\u043d\u044f\u0442\u043d\u0443\u044e \u0441\u0445\u0435\u043c\u0443 \u0434\u043b\u044f \u0432\u0430\u0448\u0435\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u044b.<\/p>\n<h2>\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u0443\u0435\u043c \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b<\/h2>\n<p>\u041d\u043e \u044d\u0442\u043e \u0431\u044b\u043b\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430\u0447\u0430\u043b\u043e. \u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u043c\u044b \u0440\u0435\u0448\u0438\u043b\u0438 \u0440\u0430\u0441\u043a\u0440\u044b\u0442\u044c \u043f\u043e\u0442\u0435\u043d\u0446\u0438\u0430\u043b TypeScript.<\/p>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/aa0\/23f\/ec4\/aa023fec46c734731e5d54e71bb38a55.png\" width=\"1078\" height=\"504\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/aa0\/23f\/ec4\/aa023fec46c734731e5d54e71bb38a55.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/aa0\/23f\/ec4\/aa023fec46c734731e5d54e71bb38a55.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u0418\u0434\u0435\u044f \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u0441\u0432\u044f\u0437\u0430\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u0438 \u043e\u0431\u044a\u0435\u043a\u0442 \u043f\u0443\u0442\u0435\u0439 <strong>Routes<\/strong>.<\/p>\n<p>\u0415\u0441\u0442\u044c \u0434\u0432\u0430 \u0442\u0438\u043f\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432:<\/p>\n<ul>\n<li>\n<p><strong>Path<\/strong>: \/example\/:<strong>id<\/strong><\/p>\n<\/li>\n<li>\n<p><strong>Query<\/strong>: \/example?<strong>id<\/strong>=123<\/p>\n<\/li>\n<\/ul>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0441\u043f\u0435\u0440\u0432\u0430 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043c \u043f\u0440\u043e\u0442\u043e\u0442\u0438\u043f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445. \u0411\u044b\u043b\u043e \u0431\u044b \u043a\u043b\u0430\u0441\u0441\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0432 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u0443\u044e \u0443\u0442\u0438\u043b\u0438\u0442\u0443 \u043d\u0430\u0448 \u043f\u0443\u0442\u044c \u0438\u043b\u0438 \u0435\u0433\u043e \u0442\u0438\u043f \u0438\u0437 <strong>Routes<\/strong>, \u0430 \u0432 \u043e\u0442\u0432\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b. \u0422\u043e\u0433\u0434\u0430 \u0432\u0441\u0435 \u0442\u0438\u043f\u044b \u043c\u044b \u0441\u043c\u043e\u0433\u043b\u0438 \u0431\u044b \u043e\u043f\u0438\u0441\u0430\u0442\u044c \u0435\u0434\u0438\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u043e \u0432 <strong>Routes<\/strong>. \u041d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 React-\u0445\u0443\u043a\u0430:<\/p>\n<pre><code class=\"typescript\">const { id, type } = usePageParams&lt;typeof Routes.tasks.item&gt;();<\/code><\/pre>\n<p><strong>\u0420\u0430\u0437\u0431\u0435\u0440\u0435\u043c \u043f\u043e\u0448\u0430\u0433\u043e\u0432\u043e, \u043a\u0430\u043a \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f:<\/strong><\/p>\n<p>1. \u0421\u043f\u0435\u0440\u0432\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u0443\u0435\u043c \u043f\u0443\u0442\u0438 \u0432 Routes.<\/p>\n<p>\u0414\u043b\u044f \u043f\u0440\u043e\u0431\u0440\u043e\u0441\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0442\u0430\u043a\u043e\u0433\u043e \u0432\u0438\u0434\u0430:<\/p>\n<pre><code class=\"typescript\">export const Routes = {   ...   tasks: {     item: (id: number, type: string) =&gt; `\/tasks\/${id}?type=${type}`   } }<\/code><\/pre>\n<p>\u0410 \u0447\u0442\u043e\u0431\u044b \u0443\u043f\u0440\u043e\u0441\u0442\u0438\u0442\u044c \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044e \u0442\u0430\u043a\u0438\u0445 \u0444\u0443\u043d\u043a\u0446\u0438\u0439, \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u0443\u044e \u0443\u0442\u0438\u043b\u0438\u0442\u0443, \u043a\u043e\u0442\u043e\u0440\u0430\u044f:<\/p>\n<ul>\n<li>\n<p>\u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u043f\u0443\u0442\u044c \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u0430 (\u0435\u0441\u043b\u0438 path-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u043d\u0435\u0442 \u2014 \u0441\u0442\u0440\u043e\u043a\u0443, \u0430 \u0435\u0441\u043b\u0438 \u0435\u0441\u0442\u044c \u2014 \u0444\u0443\u043d\u043a\u0446\u0438\u044e).<\/p>\n<\/li>\n<li>\n<p>\u0431\u0443\u0434\u0435\u0442 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u043f\u0443\u0442\u0438 \u0441 \u0432\u043d\u0435\u0434\u0440\u0435\u043d\u0438\u0435\u043c path \u0438 query-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432:<\/p>\n<\/li>\n<\/ul>\n<p>\u0422\u0430\u043a\u0436\u0435 \u0434\u043b\u044f \u0443\u0434\u043e\u0431\u0441\u0442\u0432\u0430 \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u043c <strong>path<\/strong> \u0438 <strong>query<\/strong> \u043d\u0430 \u0440\u0430\u0437\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0438 \u0432\u0432\u0435\u0434\u0451\u043c \u043f\u0440\u043e\u0441\u0442\u0435\u043d\u044c\u043a\u0438\u0439 \u0445\u0435\u043b\u043f\u0435\u0440 <strong>createUrl<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0441\u043a\u043b\u0435\u0438\u0432\u0430\u0442\u044c \u043e\u0440\u0438\u0434\u0436\u0438\u043d, \u043f\u0443\u0442\u044c, \u043a\u0432\u0435\u0440\u0438 \u0438 \u0445\u044d\u0448 \u0432 \u0438\u0442\u043e\u0433\u043e\u0432\u044b\u0439 URL.<\/p>\n<pre><code class=\"typescript\">import { stringify } from 'qs';  export type TParametrizedRoute&lt;TPath = any, TQuery = any&gt; = (params: {   path?: TPath;   query?: TQuery; }) =&gt; string;  \/\/ \u0423\u0442\u0438\u043b\u0438\u0442\u0430 \u0433\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440 \u0444\u0443\u043d\u043a\u0446\u0438\u0439: \/\/ - \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u043c \u043f\u0443\u0442\u044c \u0432 \u0432\u0438\u0434\u0435 \u0441\u0442\u0440\u043e\u043a\u0438 \u0438\u043b\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \/\/ - \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u0438\u0440\u0443\u0435\u0442 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438 function parametrizedRoute&lt;TPath = undefined, TQuery = undefined&gt;(   pathOrGetPath: TPath extends undefined ? string : (path: TPath) =&gt; string ): TParametrizedRoute&lt;TPath, TQuery&gt; {   return ({ path, query }: { path?: TPath; query?: TQuery }) =&gt; {     return createUrl({       path: typeof pathOrGetPath === 'function'               ? pathOrGetPath(path!)               : pathOrGetPath || '',       query     });   }; }  \/\/ \u0412 \u0441\u0442\u0430\u0442\u044c\u0435 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u043f\u0440\u0438\u043c\u0438\u0442\u0438\u0432\u043d\u0430\u044f \u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u0430\u044f \u0443\u0442\u0438\u043b\u0438\u0442\u0430 \u0434\u043b\u044f \u0441\u043a\u043b\u0435\u0439\u043a\u0438 \u0447\u0430\u0441\u0442\u0435\u0439 URL\u0430. \/\/ \u041f\u0440\u0438\u043c\u0435\u0440 \u0433\u043e\u0442\u043e\u0432\u043e\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438: https:\/\/github.com\/meabed\/build-url-ts export const createUrl = (args: {   origin?: string;   path?: string;   query?: Record&lt;string, unknown&gt;;   hash?: string; }): string =&gt; {   return [     args.origin || '',     args.path || '',     args.query ? `?${stringify(args.query)}` : '',     args.hash ? `#${args.hash}` : ''   ].join(''); };<\/code><\/pre>\n<p>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043d\u043e\u0432\u0443\u044e \u0443\u0442\u0438\u043b\u0438\u0442\u0443 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043f\u0443\u0442\u0435\u0439, \u0435\u0441\u0442\u044c \u0447\u0435\u0442\u044b\u0440\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f:<\/p>\n<pre><code class=\"typescript\">export const Routes = {   tasks: {     \/\/ \u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0435\u0441\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e query - \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u043c \u0441\u0442\u0440\u043e\u043a\u0443 \u043f\u0443\u0442\u0438:     index: parametrizedRoute&lt;undefined, { userId?: number; type?: string }&gt;(       '\/tasks'     )      \/\/ \u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0435\u0441\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e path:     item: parametrizedRoute&lt;{ id: number }&gt;((params) =&gt; {       return `\/tasks\/${params.id}`;     }),      \/\/ \u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0435\u0441\u0442\u044c \u0438 path, \u0438 query:     item: parametrizedRoute&lt;{ id: number }, { type?: string }&gt;((params) =&gt; {       return `\/tasks\/${params.id}`;     }),      \/\/ \u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043d\u0435\u0442 \u043d\u0438 query, \u043d\u0438 path:     list: '\/tasks\/list'   } };<\/code><\/pre>\n<p>2. \u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u0442\u0438\u043f\u044b \u0434\u043b\u044f \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432:<\/p>\n<pre><code class=\"typescript\">\/\/ \u0422\u0438\u043f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u0443\u0442\u0438 \u0443\u0436\u0435 \u0432\u0432\u0435\u043b\u0438 \u0440\u0430\u043d\u0435\u0435:  export type TParametrizedRoute&lt;TPath = any, TQuery = any&gt; = (params: {   path?: TPath;   query?: TQuery; }) =&gt; string;  \/\/ \u041f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u0432\u0441\u0435 \u043a\u043e\u043d\u0435\u0447\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0432 URL'\u0435 \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u044f\u0442\u0441\u044f \u0432 \u0441\u0442\u0440\u043e\u043a\u0438, \u0442\u043e \u043b\u0443\u0447\u0448\u0435 \u043f\u0440\u0438\u0432\u0435\u0441\u0442\u0438 \u043a \u043d\u0438\u043c, \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 \u0442\u0438\u043f-\u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0435\u0440: export type TObjectFieldsToStrings&lt;T&gt; = {   [K in keyof T]: T[K] extends string | undefined ? T[K] : string; };   \/\/ \u0423\u0442\u0438\u043b\u0438\u0442\u0430 \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u0442 \u0438 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u0443\u0435\u0442 \u043d\u0443\u0436\u043d\u043e\u0435 \u043f\u043e\u043b\u0435 \u0438\u0437 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u0443\u0442\u0438 type TExtractRouteParams&lt;   T extends TParametrizedRoute,   K extends keyof Parameters&lt;T&gt;[0] &gt; = TObjectFieldsToStrings&lt;   NonNullable&lt;Parameters&lt;T&gt;[0][K]&gt; &gt;;  \/\/ \u0423\u0442\u0438\u043b\u0438\u0442\u0430 \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 path \u0438\u0437 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u0443\u0442\u0438 export type TExtractPageParams&lt;T extends TParametrizedRoute&gt;   = TExtractRouteParams&lt;T, 'path'&gt;;  \/\/ \u0423\u0442\u0438\u043b\u0438\u0442\u0430 \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 query \u0438\u0437 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u0443\u0442\u0438 export type TExtractPageQuery&lt;T extends TParametrizedRoute&gt;   = TExtractRouteParams&lt;T, 'query'&gt;;<\/code><\/pre>\n<p>3. \u0418 \u0443\u0442\u0438\u043b\u0438\u0442\u044b \u0434\u043b\u044f \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432:<\/p>\n<pre><code class=\"typescript\">import { stringify } from 'qs'; import { useCallback, useMemo } from 'react'; import { useParams } from 'react-router'; import { URLSearchParamsInit, useSearchParams } from 'react-router-dom';  \/**  * \u0423\u0442\u0438\u043b\u0438\u0442\u0430 \u0434\u043b\u044f \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b (\u0438\u0437 path + \u0438\u0437 query)  *\/ export function usePageParams&lt;T extends TParametrizedRoute&gt;(): {   path: TExtractPageParams&lt;T&gt;;   query: Partial&lt;TExtractPageQuery&lt;T&gt;&gt;;   setParams: ReturnType&lt;typeof useQueryParams&lt;TExtractPageQuery&lt;T&gt;&gt;&gt;[1]; } {   const path = useParams() as TExtractPageParams&lt;T&gt;;   const [query, setParams] = useQueryParams&lt;TExtractPageQuery&lt;T&gt;&gt;();    return { path, query, setParams }; }   \/**  * \u0423\u0442\u0438\u043b\u0438\u0442\u0430 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 query-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438, \u0435\u0441\u043b\u0438 \u0435\u0451 \u043d\u0435\u0442 \u0432\u043e \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0435  *\/ export function useQueryParams&lt;T = Record&lt;string, unknown&gt;&gt;(   defaultValues?: URLSearchParamsInit ): [Partial&lt;T&gt;, (values: Partial&lt;T&gt;, replace?: boolean) =&gt; void] {   const [value, setValue] = useSearchParams(defaultValues);    const parsedQueryParams = useMemo(     () =&gt; Object.fromEntries(value.entries()) as Partial&lt;T&gt;,     [value]   );    const setQueryParams = useCallback(     (values: Partial&lt;T&gt;, replace: boolean = true) =&gt; {       setValue(stringify({ ...parsedQueryParams, ...values }), { replace });     },     [parsedQueryParams]   );    return [parsedQueryParams, setQueryParams]; }<\/code><\/pre>\n<p>4. \u0418\u0442\u043e\u0433\u043e\u0432\u043e\u0435 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0442\u0430\u043a:<\/p>\n<pre><code class=\"typescript\">export const TaskPage = (): ReactElement =&gt; {   const params = usePageParams&lt;typeof Routes.tasks.item&gt;();   const { path: { id }, query: { type } } = params;    return ( ... ); }<\/code><\/pre>\n<p>\u0413\u043e\u0442\u043e\u0432\u043e! \u0410 \u0430\u0432\u0442\u043e\u043a\u043e\u043c\u043f\u043b\u0438\u0442 \u0432 IDE \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u043f\u043e\u0434\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043f\u0440\u0438 \u0438\u0445 \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u0438.<\/p>\n<h2>\u0423\u043b\u0443\u0447\u0448\u0430\u0435\u043c \u0441\u0445\u0435\u043c\u0443: \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0438\u0437 \u0441\u0442\u0440\u043e\u043a<\/h2>\n<p>\u0410 \u0447\u0442\u043e, \u0435\u0441\u043b\u0438 \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u0440\u0443\u0447\u043d\u043e\u0433\u043e \u0443\u043a\u0430\u0437\u0430\u043d\u0438\u044f path-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432? \u0412\u0435\u0434\u044c \u043e\u0431\u044b\u0447\u043d\u043e \u043e\u043d\u0438 \u0443\u0436\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u0441\u044f \u0432 \u043f\u0443\u0442\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b. \u0418 \u044d\u0442\u043e\u0442 \u0436\u0435 \u0441\u0442\u0440\u043e\u043a\u043e\u0432\u044b\u0439 \u043f\u0443\u0442\u044c \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u0442\u0441\u044f \u0432 Router:<\/p>\n<pre><code class=\"typescript\">{   path: '\/tasks\/:id',   element: &lt;TaskPage \/&gt; }<\/code><\/pre>\n<p>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u0443\u043f\u0440\u043e\u0449\u0451\u043d\u043d\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e. \u041c\u044b \u0445\u043e\u0442\u0438\u043c \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0441\u0442\u0440\u043e\u043a\u0443 \u043f\u0443\u0442\u0438 \u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0438\u0437\u0432\u043b\u0435\u0447\u044c \u0438\u0437 \u043d\u0435\u0451 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b. \u0412\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u043c\u043e\u0449\u043d\u044b\u043c\u0438 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044f\u043c\u0438 \u044f\u0437\u044b\u043a\u0430 TypeScript: \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c <a href=\"https:\/\/www.typescriptlang.org\/docs\/handbook\/2\/conditional-types.html\" rel=\"noopener noreferrer nofollow\">infer<\/a> \u0438 <a href=\"https:\/\/www.typescriptlang.org\/docs\/handbook\/2\/template-literal-types.html\" rel=\"noopener noreferrer nofollow\">Template Literal Types<\/a>.<\/p>\n<pre><code class=\"typescript\">\/**  * \u0422\u0438\u043f, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442 \u0441\u0442\u0440\u043e\u043a\u0443 \u0438 \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b  *\/ type TExtractParams&lt;T extends string&gt; =   \/\/ \u041f\u0435\u0440\u0432\u044b\u0439 \u0441\u043b\u0443\u0447\u0430\u0439: \u0441\u0442\u0440\u043e\u043a\u0430 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 `:param\/\u0447\u0442\u043e-\u0442\u043e`   T extends `${string}:${infer Param}\/${infer Rest}`     \/\/ \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0442\u0438\u043f \"\u0441\u0442\u0440\u043e\u043a\u0430\" \u043f\u043e \u0438\u043c\u0435\u043d\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430     \/\/ \u0438 \u0440\u0435\u043a\u0443\u0440\u0441\u0438\u0432\u043d\u043e \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u043e\u0441\u0442\u0430\u0432\u0448\u0443\u044e\u0441\u044f \u0447\u0430\u0441\u0442\u044c \u0441\u0442\u0440\u043e\u043a\u0438     ? { [K in Param]: string } &amp; TExtractParams&lt;`\/${Rest}`&gt;     : \/\/ \u0412\u0442\u043e\u0440\u043e\u0439 \u0441\u043b\u0443\u0447\u0430\u0439: \u0441\u0442\u0440\u043e\u043a\u0430 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 `:param` \u0431\u0435\u0437 \u0441\u043b\u044d\u0448\u0435\u0439       T extends `${string}:${infer Param}`       ? { [K in Param]: string }       : unknown;  const example: TExtractParams2&lt;'\/tasks\/:first\/:second'&gt; = {   first: '123', \/\/ \u0410\u0432\u0442\u043e\u043a\u043e\u043c\u043f\u043b\u0438\u0442 IDE \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442   second: '456', }; <\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0434\u043e\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0443\u0436\u0435 \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0439 <strong>parametrizedRoute<\/strong>, \u0447\u0442\u043e\u0431\u044b \u043e\u043d \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u043b <strong>\u043f\u0443\u0442\u044c \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430<\/strong> \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u043b \u0432\u0441\u0451 \u0442\u0430\u043a \u0436\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u043f\u0443\u0442\u0438. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u043c\u0430\u0433\u0438\u0435\u0439 <strong>\u043b\u0438\u0442\u0435\u0440\u0430\u043b\u043e\u0432 \u0432 TypeScript.<\/strong><\/p>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/41c\/54a\/955\/41c54a9553081c97ccad4baad7b0f637.png\" width=\"1034\" height=\"968\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/41c\/54a\/955\/41c54a9553081c97ccad4baad7b0f637.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/41c\/54a\/955\/41c54a9553081c97ccad4baad7b0f637.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u0410 \u0435\u0441\u043b\u0438 \u0444\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e, \u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0432\u044b\u0432\u043e\u0434 \u0442\u0438\u043f\u0430 \u0438\u0437 \u0441\u0442\u0440\u043e\u043a\u043e\u0432\u043e\u0433\u043e \u043b\u0438\u0442\u0435\u0440\u0430\u043b\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0438 <strong>&lt;T extends string&gt;(route: T).<\/strong><\/p>\n<pre><code class=\"typescript\">const Routes = {   tasks: {     item: parametrizedRoute('\/tasks\/:id')   } };  \/\/ \u041c\u044b \u043d\u0435 \u0437\u0430\u0434\u0430\u0451\u043c \u0442\u0438\u043f \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u0447\u0435\u0440\u0435\u0437 generic, \u0430 \u043d\u0430\u043e\u0431\u043e\u0440\u043e\u0442: \/\/ \u043e\u043d \u0441\u0430\u043c \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442\u0441\u044f \u0438\u0437 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u0430, \u0438 \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e \u0434\u0430\u043b\u0435\u0435 function parametrizedRoute&lt;T extends string&gt;(route: T): TParametrizedRoute&lt;T&gt; {   const preparePath = (params: TExtractParams&lt;T&gt;): string =&gt; {     \/\/ TODO: \u0437\u0430\u043c\u0435\u043d\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0432 \u043f\u0443\u0442\u0438 \u043d\u0430 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f (\u0441\u0434\u0435\u043b\u0430\u0435\u043c \u0434\u0430\u043b\u0435\u0435)     return '';   };   return ({ path }: { path: TExtractParams&lt;T&gt; }): string =&gt; {     return createUrl({ path: preparePath(path) });   }; }  \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c: navigate(   Routes.tasks.item({     path: {       id: '123' \/\/ \u0410\u0432\u0442\u043e\u043a\u043e\u043c\u043f\u043b\u0438\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442     }   }) );<\/code><\/pre>\n<p>\u041d\u043e \u0437\u0434\u0435\u0441\u044c \u0435\u0441\u0442\u044c \u0432\u0430\u0436\u043d\u044b\u0439 \u043d\u044e\u0430\u043d\u0441: \u043d\u0430\u043c \u0432\u0435\u0434\u044c \u043d\u0443\u0436\u043d\u044b \u0435\u0449\u0451 \u0438 <strong>query<\/strong>-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b.<\/p>\n<p>\u0410 \u0435\u0441\u043b\u0438 \u043c\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0438\u0445 \u0432\u0442\u043e\u0440\u044b\u043c \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u043e\u043c, \u0442\u043e \u043c\u0430\u0433\u0438\u044f \u043b\u0438\u0442\u0435\u0440\u0430\u043b\u043e\u0432 \u0432\u044b\u043a\u043b\u044e\u0447\u0438\u0442\u0441\u044f \u0438 \u0431\u0443\u0434\u0435\u043c \u0432\u044b\u043d\u0443\u0436\u0434\u0435\u043d\u044b \u043f\u0440\u043e\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0442\u044c \u043f\u0443\u0442\u044c \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0434\u0436\u0435\u043d\u0435\u0440\u0438\u043a\u0430, \u0432\u043e\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"typescript\">function parametrizedRoute&lt;P extends string, Q extends Record&lt;string, unknown&gt;&gt;(   path: P,   query: Q ): TParametrizedRoute&lt;P, Q&gt; {   ... }  const Routes = {   tasks: {                   \u2193\u2193\u2193 \u0434\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435                         item: parametrizedRoute&lt;'\/tasks\/:id', { type: string }&gt;('\/tasks\/:id')   } };<\/code><\/pre>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/4b6\/8a1\/6bf\/4b68a16bf95875687cd1add5235fa7f4.png\" width=\"1201\" height=\"900\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/4b6\/8a1\/6bf\/4b68a16bf95875687cd1add5235fa7f4.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/4b6\/8a1\/6bf\/4b68a16bf95875687cd1add5235fa7f4.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u041d\u043e \u0442\u0430\u043a\u043e\u0435 \u0434\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043a\u043e\u0434\u0430 \u043d\u0430\u043c \u0441\u043e\u0432\u0441\u0435\u043c \u043d\u0438 \u043a \u0447\u0435\u043c\u0443.<\/p>\n<p>\u0422\u043e\u0433\u0434\u0430 \u043d\u0443\u0436\u043d\u043e \u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0443 <strong>parametrizedRoute<\/strong> \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 generic-\u0442\u0438\u043f. \u0410 <strong>query<\/strong> \u043f\u0440\u0438\u0432\u044f\u0436\u0435\u043c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0446\u0435\u043f\u043e\u0447\u043a\u0438 \u0432\u044b\u0437\u043e\u0432\u043e\u0432 \u0442\u0430\u043c, \u0433\u0434\u0435 \u044d\u0442\u043e \u043d\u0443\u0436\u043d\u043e:<\/p>\n<pre><code class=\"typescript\">const Routes = {   tasks: {     \/\/ \u043f\u0440\u0438\u043c\u0435\u0440 \u0431\u0435\u0437 query:     itemNoQuery: parametrizedRoute('\/tasks\/:id'),     \/\/ \u043f\u0440\u0438\u043c\u0435\u0440 \u0441 query:     itemWithQuery: parametrizedRoute('\/tasks\/:id').withQuery&lt;{       type: number;     }&gt;()   } };<\/code><\/pre>\n<p>\u0414\u043e\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u0443\u0442\u0438\u043b\u0438\u0442\u044b \u0438 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043d\u0430 \u0432\u0435\u0441\u044c \u043a\u043e\u0434:<\/p>\n<pre><code class=\"typescript\">\/**  * \u0422\u0438\u043f, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442 \u0441\u0442\u0440\u043e\u043a\u0443 \u0438 \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b  *\/ export type TExtractParams&lt;T extends string&gt;   = T extends `${string}:${infer Param}\/${infer Rest}`   ? { [K in Param]: string } &amp; TExtractParams&lt;`\/${Rest}`&gt;   : T extends `${string}:${infer Param}`     ? { [K in Param]: string }     : unknown;  \/**  * \u0418\u0442\u043e\u0433\u043e\u0432\u044b\u0439 \u0442\u0438\u043f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u0443\u0442\u0438:  * - \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0441 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u043e\u043c path-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432.  * - \u043f\u043e\u043b\u0435-\u0444\u0443\u043d\u043a\u0446\u0438\u044f withQuery, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0441 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u0430\u043c\u0438 path \u0438 query-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432.  * - \u043f\u043e\u043b\u0435 route \u0432 \u043a\u0430\u0436\u0434\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0441 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u043c \u0440\u043e\u0443\u0442\u043e\u043c (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0434\u043b\u044f React Router'\u0430)  *\/ type TParametrizedRoute&lt;T extends string&gt; = {   ({ path }: { path: TExtractParams&lt;T&gt; }): string;   route: string;    withQuery&lt;TQuery extends Record&lt;string, unknown&gt;&gt;(): {     ({ path, query }: { path: TExtractParams&lt;T&gt;; query: TQuery }): string;     route: string;   }; };  \/**  * \u0423\u0442\u0438\u043b\u0438\u0442\u0430 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u0443\u0442\u0438.  * \u0417\u043d\u0430\u0447\u0435\u043d\u0438\u044f path \u0438 query-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u043e\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f  * \u0432 \u043f\u0443\u0442\u044c \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043f\u0440\u0438 \u043a\u043e\u043d\u0435\u0447\u043d\u043e\u043c \u0432\u044b\u0437\u043e\u0432\u0435  *\/ function parametrizedRoute&lt;T extends string&gt;(route: T): TParametrizedRoute&lt;T&gt; {   const preparePath = (params: TExtractParams&lt;T&gt;): string =&gt; {     let processed: string = route;     Object.keys(params).forEach((key) =&gt; {       const value = params[key];       if (typeof value === 'string') {         processed = processed.replace(`:${key}`, value);       }     });     return processed;   };    const result = ({ path }: { path: TExtractParams&lt;T&gt; }): string =&gt; {     return createUrl({ path: preparePath(path) });   };   result.route = route;    result.withQuery = &lt;TQuery extends Record&lt;string, unknown&gt;&gt;() =&gt; {     const fn = ({ path, query }: { path: TExtractParams&lt;T&gt;; query: TQuery }): string =&gt; {       return createUrl({ path: preparePath(path), query });     };     fn.route = route;     return fn;   };    return result; }  \/**  * \u0423\u0442\u0438\u043b\u0438\u0442\u0430 \u0434\u043b\u044f \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b (\u0438\u0437 path + \u0438\u0437 query)  *\/ function usePageParams&lt;T extends (args: any) =&gt; string&gt;(_route: T) {   type RouteParams = Parameters&lt;T&gt;[0];   type P = RouteParams extends { path: infer Path } ? Path : never;   type Q = RouteParams extends { query: infer Query } ? Query : never;    const path = useParams() as P;   const [query, setParams] = useQueryParams&lt;Q&gt;();    return { path, query, setParams }; }<\/code><\/pre>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 usePageParams \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u043e\u0441\u044c, \u0442\u0435\u043f\u0435\u0440\u044c \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c \u0440\u043e\u0443\u0442 \u043d\u0435 \u0447\u0435\u0440\u0435\u0437 \u0442\u0438\u043f, \u0430 \u0432 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u044b:<\/p>\n<pre><code class=\"typescript\">const {   path: { taskId },   query: { type }, } = usePageParams(Routes.tasks.item);<\/code><\/pre>\n<details class=\"spoiler\">\n<summary>\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0447\u0442\u043e \u0432\u0441\u0451 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0438 IDE \u043f\u043e\u0434\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u0442\u0438\u043f\u044b \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e:<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"typescript\">export const useExample = (): void =&gt; {   const navigate = useNavigate();    \/\/ \u0412\u0421\u0401 \u041e\u041a   navigate(     Routes.tasks.itemWithQuery({       path: {         id: '123'       },       query: {         type: 456       }     })   );   \/\/ \u0412\u0421\u0401 \u041e\u041a   const withQuery = usePageParams(Routes.tasks.itemWithQuery);   console.log(withQuery.path.id); \/\/ '123' - string!   console.log(withQuery.query.type); \/\/ 456   console.log(withQuery.route); \/\/ 'tasks\/:id'    \/\/ \u0412\u0421\u0401 \u041e\u041a   navigate(     Routes.tasks.itemNoQuery({       path: {         id: 123       }     })   );   \/\/ \u0412\u0421\u0401 \u041e\u041a   const noQuery = usePageParams(Routes.tasks.itemNoQuery);   console.log(noQuery.path.id); \/\/ 123 - number!   console.log(noQuery.route); \/\/ 'tasks\/:id' };<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0421 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u043e\u043b\u0435\u0439 &#171;<strong>route<\/strong>&#187; \u043e\u0431\u044a\u0435\u043a\u0442 <strong>Routes<\/strong> \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043a\u0430\u043a \u0435\u0434\u0438\u043d\u044b\u0439 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u043f\u0440\u0430\u0432\u0434\u044b \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0440\u043e\u0443\u0442\u0435\u0440\u0430. \u042d\u0442\u043e \u0438\u0441\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0440\u0430\u0441\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u044f \u0438 \u0443\u043f\u0440\u043e\u0449\u0430\u0435\u0442 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443:<\/p>\n<pre><code class=\"typescript\">export const browserRouter = createBrowserRouter([   {     children: [       {         path: Routes.tasks.index.route,         element: &lt;div&gt;INDEX&lt;\/div&gt;       },       {         path: Routes.tasks.item.route,         element: &lt;div&gt;ITEM&lt;\/div&gt;       }     ]   } ]); <\/code><\/pre>\n<p>\u0425\u043c, \u0430 \u0415\u0429\u0401 \u0443\u043b\u0443\u0447\u0448\u0438\u0442\u044c \u043c\u043e\u0436\u043d\u043e \u043a\u0430\u043a-\u0442\u043e?<\/p>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/ebd\/ceb\/250\/ebdceb2507e5b775ae307da6de526e86.png\" width=\"1302\" height=\"886\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/ebd\/ceb\/250\/ebdceb2507e5b775ae307da6de526e86.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/ebd\/ceb\/250\/ebdceb2507e5b775ae307da6de526e86.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<h2>\u0422\u0438\u043f\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 path-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b<\/h2>\n<p>\u041d\u0435 \u0432\u0441\u0435\u0433\u0434\u0430 path-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u2014 \u044d\u0442\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u0442\u0440\u043e\u043a\u0438 \u0438\u043b\u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0438\u0441\u043b\u0430. \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0442\u0438\u043f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432:<\/p>\n<pre><code class=\"typescript\">const Routes = {   tasks: {     itemString: parametrizedRoute('\/tasks\/:id&lt;string&gt;'),     itemNumber: parametrizedRoute('\/tasks\/:id&lt;number&gt;'),     itemCustom: parametrizedRoute('\/tasks\/:id&lt;custom&gt;').withQuery&lt;...&gt;(),   } };<\/code><\/pre>\n<p>\u00ab\u0410 \u0447\u0442\u043e \u0437\u0430 <strong>custom<\/strong> \u0442\u0430\u043a\u043e\u0439?\u00bb, \u2014 \u0441\u043f\u0440\u043e\u0441\u0438\u0442\u0435 \u0432\u044b. \u042d\u0442\u043e \u043c\u044b \u0442\u0430\u043a \u0432\u043d\u0435\u0434\u0440\u044f\u0435\u043c \u0433\u0438\u0431\u043a\u043e\u0441\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0445 \u0442\u0438\u043f\u043e\u0432: \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0434\u043b\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438 \u0441\u0442\u0440\u043e\u043a\u043e\u0432\u044b\u0445 \u043b\u0438\u0442\u0435\u0440\u0430\u043b\u044c\u043d\u044b\u0445 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439, \u043a\u043e\u0433\u0434\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u043e \u0438\u0437 \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439.<\/p>\n<p>\u0418 \u0435\u0449\u0451 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u043a\u043e\u0440\u043d\u0435\u0440-\u043a\u0435\u0439\u0441, \u043a\u043e\u0433\u0434\u0430 \u0435\u0441\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e <strong>query<\/strong>-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u0443\u0442\u0438\u043b\u0438\u0442\u0443 <strong>TRemoveEmptyObjects<\/strong>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u0443\u0435\u0442 \u043f\u0443\u0441\u0442\u043e\u0439 \u043e\u0431\u044a\u0435\u043a\u0442<strong> path: {}<\/strong> \u043d\u0430 \u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u0435 <strong>path?: never.<\/strong><\/p>\n<pre><code class=\"typescript\">parametrizedRoute('\/example').withQuery&lt;{ test: number }&gt;()  \u2026  type TRemoveEmptyObjects&lt;T&gt; = {   [K in keyof T as keyof T[K] extends never ? never : K]: T[K]; }; type TPathParams&lt;T extends string&gt; = TRemoveEmptyObjects&lt;{ path: TExtractParams&lt;T&gt; }&gt;; <\/code><\/pre>\n<p><strong>\u041d\u043e\u0432\u044b\u0439 \u043f\u043b\u0430\u043d \u0442\u0430\u043a\u043e\u0439:<\/strong><\/p>\n<ul>\n<li>\n<p>\u041f\u0430\u0440\u0441\u0438\u043c \u0441\u0442\u0440\u043e\u043a\u0443 \u0440\u043e\u0443\u0442\u0430:<\/p>\n<ul>\n<li>\n<p>\u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c path-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b.\u0438\u0437 \u201c\u0441\u043a\u043e\u0431\u043e\u0447\u0435\u043a\u201d <strong>&lt;&#8230;&gt;<\/strong> <\/p>\n<\/li>\n<li>\n<p>\u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u0441\u0442\u0440\u043e\u043a\u043e\u0432\u044b\u0439 \u0442\u0438\u043f: \u201cstring\u201d \/ \u201cnumber\u201d \/ \u201ccustom\u201d \/ \u2026 .<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>\u041f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u044b\u0432\u0430\u0435\u043c \u0441\u0442\u0440\u043e\u043a\u043e\u0432\u044b\u0439 \u0442\u0438\u043f \u0432 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u0439 TS-\u0442\u0438\u043f: string \/ number \/ \u201ca\u201d | \u201cb\u201d \/ \u2026 .<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430 JS \u043f\u043e\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c path-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b (<strong>\/:id<\/strong>), \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u0443\u044f \u0432 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u0442\u0438\u043f.<\/p>\n<\/li>\n<\/ul>\n<p>\u0427\u0442\u043e\u0431\u044b \u043d\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0438\u0441\u0430\u0442\u044c \u043a\u043e\u0434, \u0430 \u0441\u0440\u0430\u0437\u0443 \u0435\u0433\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0438 \u043d\u0430\u0433\u043b\u044f\u0434\u043d\u043e \u0432\u0438\u0434\u0435\u0442\u044c \u0442\u043e, \u0447\u0442\u043e \u0434\u0435\u043b\u0430\u044e\u0442 \u0442\u0438\u043f\u044b \u2013 \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0442\u0430\u043a\u0436\u0435 \u0434\u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u044b \u043d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 \u0442\u0438\u043f. \u041c\u044b \u043f\u043e\u0434\u0441\u043c\u043e\u0442\u0440\u0435\u043b\u0438, \u043a\u0430\u043a \u043f\u0438\u0441\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u044b, <a href=\"https:\/\/habr.com\/ru\/companies\/cloud_ru\/articles\/865118\/\" rel=\"noopener noreferrer nofollow\">\u0432 \u0441\u0442\u0430\u0442\u044c\u0435 \u0443 \u041a\u043e\u0441\u0442\u0438 \u041b\u043e\u0433\u0438\u043d\u043e\u0432\u0441\u043a\u0438\u0445.<\/a><\/p>\n<p><strong>\u0418\u0442\u043e\u0433\u043e\u0432\u044b\u0439 \u043a\u043e\u0434 \u0441 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u043c\u0438:<\/strong><\/p>\n<pre><code class=\"typescript\">\/*  * \u041c\u0430\u043f\u044b \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u0442\u0440\u043e\u043a\u0438 \u0432 \u0444\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0442\u0438\u043f  *\/ type TDefaultType = number; type TParamTypeMap = {   string: string;   number: number;   custom: 'a' | 'b'; }; type TTypeConverters = {   [K in keyof TParamTypeMap]: (v: string | undefined) =&gt; TParamTypeMap[K] | undefined; }; export const TYPE_CONVERTERS: TTypeConverters = {   string: (v) =&gt; String(v),   number: (v) =&gt; (isNaN(Number(v)) ? undefined : Number(v)),   custom: (v) =&gt; (v === 'a' ? 'a' : 'b'), };  export const getParamRegExp = (key: string): RegExp =&gt; new RegExp(`:${key}(?:&lt;([\\\\w]+)&gt;)?`);  \/**  * \u0422\u0438\u043f, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043a\u043e\u043f\u0438\u0440\u0443\u0435\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u043d\u044b\u0439 \u0442\u0438\u043f \u0438 \u0442\u0435\u043c \u0441\u0430\u043c\u044b\u043c \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0438\u0442 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0432 \u043e\u0434\u0438\u043d  * {id: number} &amp; {type: string} =&gt; { id: number; type: string }.  * \u042d\u0442\u043e \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u043e \u0443\u043b\u0443\u0447\u0448\u0438\u0442 \u043e\u0448\u0438\u0431\u043a\u0438 \u0432 IDE  * @see https:\/\/www.totaltypescript.com\/concepts\/the-prettify-helper  *\/ type TPrettify&lt;T&gt; = {   [K in keyof T]: T[K]; } &amp; {};  type TPrettifyTests = [   Expect&lt;     Equals&lt;       TPrettify&lt;{ first: string } &amp; { second: { third: number } }&gt;,       { first: string; second: { third: number } }     &gt;   &gt;, ];  \/**  * \u0422\u0438\u043f, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u0442\u0438\u043f path-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430  *\/ type TGetParamType&lt;Param extends string&gt; =   \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043b\u0438 \u0441\u0442\u0440\u043e\u043a\u0430 \u0438\u043c\u044f \u0438 \u0442\u0438\u043f   Param extends `${infer NamePart}&lt;${infer TypePart}&gt;`     ? \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0443\u043a\u0430\u0437\u0430\u043d \u043b\u0438 \u0442\u0430\u043a\u043e\u0439 \u0442\u0438\u043f \u0432 \u043c\u0430\u043f\u0435       TypePart extends keyof TParamTypeMap       ? \/\/ \u0422\u0438\u043f \u0443\u043a\u0430\u0437\u0430\u043d \u0432 \u043c\u0430\u043f\u0435 -&gt; \u0431\u0435\u0440\u0451\u043c \u0442\u0438\u043f \u0438\u0437 \u043c\u0430\u043f\u044b         { [K in NamePart]: TParamTypeMap[TypePart] }       : \/\/ \u0422\u0438\u043f \u043d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d \u0432 \u043c\u0430\u043f\u0435 -&gt; \u0434\u0435\u0444\u043e\u043b\u0442\u043d\u044b\u0439         { [K in NamePart]: TDefaultType }     : \/\/ \u0422\u0438\u043f \"&lt;type&gt;\" \u043d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d -&gt; \u0434\u0435\u0444\u043e\u043b\u0442\u043d\u044b\u0439       { [K in Param]: TDefaultType };  type TGetParamTypeTests = [   Expect&lt;Equals&lt;TGetParamType&lt;'param&lt;string&gt;'&gt;, { param: string }&gt;&gt;,   Expect&lt;Equals&lt;TGetParamType&lt;'param&lt;number&gt;'&gt;, { param: number }&gt;&gt;,   Expect&lt;Equals&lt;TGetParamType&lt;'param&lt;custom&gt;'&gt;, { param: 'a' | 'b' }&gt;&gt;, ];  \/**  * \u0422\u0438\u043f, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442 \u0441\u0442\u0440\u043e\u043a\u0443 \u0438 \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0441 \u0443\u0447\u0451\u0442\u043e\u043c &lt;type&gt;  *\/ export type TExtractParams&lt;T extends string&gt; = TPrettify&lt;   T extends `${string}:${infer Param}\/${infer Rest}`     ? TGetParamType&lt;Param&gt; &amp; TExtractParams&lt;`\/${Rest}`&gt;     : T extends `${string}:${infer Param}`       ? TGetParamType&lt;Param&gt;       : unknown &gt;;  type TExtractParamsTests = [   Expect&lt;Equals&lt;TExtractParams&lt;'\/:first'&gt;, { first: TDefaultType }&gt;&gt;,   Expect&lt;Equals&lt;TExtractParams&lt;'\/start\/:first&lt;string&gt;\/end'&gt;, { first: string }&gt;&gt;,   Expect&lt;     Equals&lt;       TExtractParams&lt;'\/start\/:first&lt;number&gt;\/middle\/:second\/end'&gt;,       { first: number; second: TDefaultType }     &gt;   &gt;,   Expect&lt;     Equals&lt;       TExtractParams&lt;'\/:first&lt;number&gt;\/:second\/:third&lt;custom&gt;'&gt;,       { first: number; second: TDefaultType; third: 'a' | 'b' }     &gt;   &gt;, ];  \/**  * \u041e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0439, \u043a\u043e\u0433\u0434\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u0432\u0435\u0440\u0438: parametrizedRoute('\/example').withQuery&lt;{ test: number }&gt;(),  * \u041f\u0443\u0441\u0442\u043e\u0439 \u043e\u0431\u044a\u0435\u043a\u0442 { path: {} } \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d \u0432 \u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u0435 { path?: never }  *\/ type TRemoveEmptyObjects&lt;T&gt; = {   [K in keyof T as keyof T[K] extends never ? never : K]: T[K]; };  type TRemoveEmptyObjectsTests = [   Expect&lt;     Equals&lt;       TRemoveEmptyObjects&lt;{ first: {}; second: { example: string }; third: {} }&gt;,       { second: { example: string } }     &gt;   &gt;, ];  \/**  * \u0422\u0438\u043f, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 \u0438 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u044b\u0432\u0430\u0435\u0442 path-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043f\u0440\u0438 \u0438\u0445 \u043d\u0430\u043b\u0438\u0447\u0438\u0438  *\/ type TPathParams&lt;T extends string&gt; = TRemoveEmptyObjects&lt;{ path: TExtractParams&lt;T&gt; }&gt;;  type TPathParamsTests = [   Expect&lt;Equals&lt;TPathParams&lt;'\/:first&lt;string&gt;'&gt;, { path: { first: string } }&gt;&gt;,   Expect&lt;Equals&lt;TPathParams&lt;'\/example'&gt;, {}&gt;&gt;, ];  \/**  * \u0422\u0438\u043f, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u0443\u0435\u0442 \u0432\u0441\u0435 \u041d\u0415 \u0441\u0442\u0440\u043e\u043a\u0438 \u0432 \u0441\u0442\u0440\u043e\u043a\u0438. \u041d\u0443\u0436\u0435\u043d \u0434\u043b\u044f Query.  *\/ type TObjectFieldsToStrings&lt;T&gt; = {   [K in keyof T]: T[K] extends string | undefined ? T[K] : string; };  type TObjectFieldsToStringsTests = [   Expect&lt;     Equals&lt;       TObjectFieldsToStrings&lt;{ first: number; second: boolean; third: string }&gt;,       { first: string; second: string; third: string }     &gt;   &gt;, ];  \/**  * \u0418\u0442\u043e\u0433\u043e\u0432\u044b\u0439 \u0442\u0438\u043f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u0443\u0442\u0438:  *\/ export type TParametrizedRoute&lt;T extends string&gt; = {   \/\/ \u0437\u0434\u0435\u0441\u044c \u0442\u0435\u043f\u0435\u0440\u044c TPathParams, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u0431\u0438\u0440\u0430\u0435\u0442 \u043f\u0443\u0441\u0442\u043e\u0439 path   (params: TPathParams&lt;T&gt;): string;   \/\/ \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043f\u0443\u0442\u044c \u0432\u0438\u0434\u0430 \/example\/:id&lt;number&gt;\/inner   initialRoute: T;   \/\/ \u043f\u0443\u0442\u044c \u0434\u043b\u044f \u0440\u043e\u0443\u0442\u0435\u0440\u0430 \u0432\u0438\u0434\u0430 \/example\/:id\/inner   route: string;    withQuery&lt;TQuery extends Record&lt;string, unknown&gt;&gt;(): {     \/\/ \u0437\u0434\u0435\u0441\u044c \u0442\u0435\u043f\u0435\u0440\u044c TPathParams, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u0431\u0438\u0440\u0430\u0435\u0442 \u043f\u0443\u0441\u0442\u043e\u0439 path     (params: TPathParams&lt;T&gt; &amp; { query: TQuery }): string;     initialRoute: T;     route: string;   }; };  export function parametrizedRoute&lt;T extends string&gt;(route: T): TParametrizedRoute&lt;T&gt; {   const preparePath = (params: TPathParams&lt;T&gt;): string =&gt; {     if (!('path' in params)) {       return route;     }     let processed: string = route;     const path = params.path as TExtractParams&lt;T&gt;;     Object.keys(path).forEach((key) =&gt; {       processed = processed.replace(getParamRegExp(key), String(path[key]));     });     return processed;   };    function prepareRoute(value: string): string {     return value.replace(\/:(\\w+)&lt;[^&gt;]+&gt;\/g, ':$1');   }    const result = (params: TPathParams&lt;T&gt;): string =&gt; {     return createUrl({ path: preparePath(params) });   };   result.initialRoute = route;   result.route = prepareRoute(route);    result.withQuery = &lt;TQuery extends Record&lt;string, unknown&gt;&gt;() =&gt; {     const fn = (params: TPathParams&lt;T&gt; &amp; { query: TQuery }): string =&gt; {       return createUrl({ path: preparePath(params), query: params.query });     };     fn.initialRoute = route;     fn.route = prepareRoute(route);     return fn;   };    return result; }  \/**  * \u0423\u0442\u0438\u043b\u0438\u0442\u0430 \u0434\u043b\u044f \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b (\u0438\u0437 path + \u0438\u0437 query)  *\/ export function usePageParams&lt;   Route extends string,   Fn extends ((args: any) =&gt; string) &amp; { initialRoute: Route }, &gt;(fn: Fn) {   type RouteParams = Parameters&lt;Fn&gt;[0];   type P = RouteParams extends { path: infer Path } ? Path : never;   \/\/ \u041a\u0432\u0435\u0440\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u044e\u0442\u0441\u044f \u0438\u043c\u0435\u043d\u043d\u043e \u0441\u0442\u0440\u043e\u043a\u0430\u043c\u0438 - \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u0443\u0435\u043c:   type Q = RouteParams extends { query: infer Query } ? TObjectFieldsToStrings&lt;Query&gt; : never;    const rawPathParams = useParams();   \/**    * \u041f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u044b\u0432\u0430\u0435\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u043a \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u044b\u043c \u0442\u0438\u043f\u0430\u043c    *\/   const path = useMemo(() =&gt; {     return Object.fromEntries(       Object.keys(rawPathParams).map((key) =&gt; {         const value = rawPathParams[key];         const match = fn.initialRoute.match(getParamRegExp(key));         const type = match?.[1];         const defaultConverter: (v: any) =&gt; TDefaultType | undefined = TYPE_CONVERTERS['number'];         const converter: (v: string | undefined) =&gt; any =           (type &amp;&amp; TYPE_CONVERTERS[type]) ?? defaultConverter;         return [key, converter?.(value)];       }),     ) as P;   }, [rawPathParams, fn.initialRoute]);    const [query, setQuery] = useQueryParams&lt;Q&gt;();    return { path, query, setQuery }; }<\/code><\/pre>\n<h2>\u0418\u0442\u043e\u0433\u0438<\/h2>\n<p>\u041c\u044b \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043b\u0438, \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u0432 TypeScript-\u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u043e\u043d\u0438 \u0431\u044b\u043b\u0438 \u0446\u0435\u043d\u0442\u0440\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u043c\u0438, \u0442\u0438\u043f\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043c\u0438 \u0438 \u0443\u0434\u043e\u0431\u043d\u044b\u043c\u0438 \u0432 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438. \u0422\u0430\u043a\u043e\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u043f\u043e\u0434\u043e\u0439\u0434\u0435\u0442\u00a0\u0431\u044b\u0441\u0442\u0440\u043e\u0440\u0430\u0441\u0442\u0443\u0449\u0438\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u043c \u0441 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e\u043c \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432: \u043e\u043d \u0441\u043d\u0438\u0436\u0430\u0435\u0442 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0434\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f, \u0434\u0435\u043b\u0430\u0435\u0442 \u043a\u043e\u0434 \u043f\u0440\u0435\u0434\u0441\u043a\u0430\u0437\u0443\u0435\u043c\u044b\u043c \u0438 \u0443\u043f\u0440\u043e\u0449\u0430\u0435\u0442 \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044e \u043f\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0443.<\/p>\n<p>\u0418 \u044d\u0442\u043e \u043b\u0438\u0448\u044c \u043e\u0434\u0438\u043d \u0438\u0437 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0445 \u043f\u043e\u0434\u0445\u043e\u0434\u043e\u0432 \u2014 \u0430\u0434\u0430\u043f\u0442\u0438\u0440\u0443\u0439\u0442\u0435 \u0435\u0433\u043e \u043f\u043e\u0434 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u0438 \u0441\u0442\u0438\u043b\u044c \u0432\u0430\u0448\u0435\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u044b. \u0421\u043f\u0430\u0441\u0438\u0431\u043e, \u0447\u0442\u043e \u0434\u043e\u0447\u0438\u0442\u0430\u043b\u0438 \u0434\u043e \u043a\u043e\u043d\u0446\u0430!<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/924208\/\"> https:\/\/habr.com\/ru\/articles\/924208\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u041f\u0440\u0438\u0432\u0435\u0442! \u042f \u0418\u043b\u044c\u044f, \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434-\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0432 \u0444\u0438\u043d\u0442\u0435\u0445-\u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438 \u0422\u043e\u0447\u043a\u0430. \u041d\u0430\u043c \u0432\u0430\u0436\u043d\u043e, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0431\u044b\u043b\u0430 \u043d\u0430 \u0432\u044b\u0441\u043e\u043a\u043e\u043c \u0443\u0440\u043e\u0432\u043d\u0435, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0434\u0435\u0441\u044f\u0442\u043a\u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0434\u043b\u044f \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u043e\u0431\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u0438\u0441\u0442\u043e\u0432 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438. \u042f \u0440\u0430\u0431\u043e\u0442\u0430\u044e \u043d\u0430\u0434 \u043e\u0434\u043d\u0438\u043c \u0438\u0437 \u0442\u0430\u043a\u0438\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432. \u041e\u043d \u0430\u043a\u0442\u0438\u0432\u043d\u043e \u0440\u0430\u0437\u0432\u0438\u0432\u0430\u0435\u0442\u0441\u044f: \u0435\u0436\u0435\u043c\u0435\u0441\u044f\u0447\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0431\u043e\u043b\u0435\u0435 10 \u043d\u043e\u0432\u044b\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u2014 \u0441\u0435\u0439\u0447\u0430\u0441 \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0438\u0445 \u0431\u043e\u043b\u044c\u0448\u0435 120.<\/p>\n<p>\u0412 \u0441\u0442\u0430\u0442\u044c\u0435 \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443, \u043a\u0430\u043a \u043c\u044b \u043f\u043e\u044d\u0442\u0430\u043f\u043d\u043e \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u0430\u043b\u0438 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u043f\u0443\u0442\u0435\u0439 \u0440\u043e\u0443\u0442\u0435\u0440\u0430 \u0438 \u0441\u0432\u044f\u0437\u0430\u043b\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u0441 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430\u043c\u0438 \u0438\u0445 \u0432\u0451\u0440\u0441\u0442\u043a\u0438. \u0422\u0430\u043a\u043e\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442 \u043f\u043e\u0432\u044b\u0441\u0438\u0442\u044c \u0447\u0438\u0442\u0430\u0435\u043c\u043e\u0441\u0442\u044c \u043a\u043e\u0434\u0430, \u0441\u043e\u043a\u0440\u0430\u0449\u0430\u0435\u0442 \u0435\u0433\u043e \u0434\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438 \u0443\u043f\u0440\u043e\u0449\u0430\u0435\u0442 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443.<\/p>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440\u044b \u0432 \u0441\u0442\u0430\u0442\u044c\u0435 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u044b \u043d\u0430 TypeScript, React \u0438 React Router, \u043d\u043e \u043f\u043e\u0434\u0445\u043e\u0434 \u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u0435\u043d \u0438 \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0442\u044c\u0441\u044f \u0432 \u0434\u0440\u0443\u0433\u0438\u0445 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044f\u0445:<\/p>\n<ul>\n<li>\n<p>React, Next.js, Vue, Svelte (Frontend).<\/p>\n<\/li>\n<li>\n<p>React Native (Mobile).<\/p>\n<\/li>\n<li>\n<p>Electron (Desktop).<\/p>\n<\/li>\n<li>\n<p>Node.js, NestJS, Express (Backend).<\/p>\n<\/li>\n<\/ul>\n<h2>\u0427\u0430\u0441\u0442\u0430\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u0443\u0442\u0435\u0439 \u0440\u043e\u0443\u0442\u0438\u043d\u0433\u0430<\/h2>\n<figure class=\"full-width\"><\/figure>\n<pre><code class=\"typescript\">export const MAIN_PAGE_PATH = '\/'; export const AUTH_PAGE_PATH = '\/login'; export const NOT_FOUND_PAGE_PATH = '\/404'; export const SEARCH_PAGE_PATH = '\/search'; export const TASKS_PAGE_PATH = '\/tasks'; export const ACTUAL_TASKS_PAGE_PATH = '\/tasks\/actual'; export const FINISHED_TASKS_PAGE_PATH = '\/tasks\/finished'; export const getTaskPagePath = (id: number, type: string): string =&gt; `\/tasks\/${id}?type=${type}`; \/\/ \u0438 \u044d\u0442\u043e \u0445\u043e\u0440\u043e\u0448\u043e \u0435\u0449\u0451, \u0435\u0441\u043b\u0438 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0432\u044b\u043d\u0435\u0441\u0435\u043d \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u043e \/\/ ...\u0438 \u0437\u0434\u0435\u0441\u044c \u0435\u0449\u0451 \u0441\u0442\u043e \u0448\u0442\u0443\u043a<\/code><\/pre>\n<p>\u041e\u0431\u044b\u0447\u043d\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445 \u0432\u044b\u0433\u043b\u044f\u0434\u044f\u0442 \u0438\u043c\u0435\u043d\u043d\u043e \u0442\u0430\u043a.<\/p>\n<p>\u042d\u0442\u0438 \u043c\u043d\u043e\u0433\u043e\u0447\u0438\u0441\u043b\u0435\u043d\u043d\u044b\u0435 \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u044b \u043f\u0440\u0438 \u0432\u0451\u0440\u0441\u0442\u043a\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u0432 \u0442\u0430\u043a\u043e\u043c \u0444\u043e\u0440\u043c\u0430\u0442\u0435:<\/p>\n<pre><code class=\"typescript\">--------- home-page.tsx: import { useNavigate } from 'react-router-dom'; \/\/ \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0443\u0442\u0438\u043b\u0438\u0442\u044b \u0434\u043b\u044f \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0438 const navigate = useNavigate(); \/\/ \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0435\u043c \u043f\u0435\u0440\u0435\u0445\u043e\u0434 \u043d\u0430 \u0434\u0440\u0443\u0433\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443, \u043f\u0440\u043e\u043a\u0438\u0434\u044b\u0432\u0430\u0435\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b navigate(getTaskPagePath(task.id, task.type))  --------- task-page.tsx: import { useParams } from 'react-router'; import { useQueryParams } from 'shared\/helpers'; \/\/ \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b const { id } = useParams&lt;{ id: number }&gt;(); const { type } = useQueryParams&lt;{ type: string }&gt;(); \/\/ \u043a\u0430\u043a-\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c id \u0438 type \u0432 \u0432\u0435\u0440\u0441\u0442\u043a\u0435 ...<\/code><\/pre>\n<p>\u041f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c \u0438 \u0440\u0430\u0437\u0432\u0438\u0432\u0430\u0442\u044c \u0442\u0430\u043a\u043e\u0439 \u043a\u043e\u0434 \u043d\u0435\u043f\u0440\u043e\u0441\u0442\u043e. \u0421\u0440\u0435\u0434\u0438 \u043e\u0447\u0435\u0432\u0438\u0434\u043d\u044b\u0445 \u043f\u0440\u043e\u0431\u043b\u0435\u043c:<\/p>\n<ol>\n<li>\n<p><strong>\u041f\u043e\u0438\u0441\u043a \u043d\u0443\u0436\u043d\u043e\u0433\u043e \u043f\u0443\u0442\u0438 \u0437\u0430\u0442\u0440\u0443\u0434\u043d\u0451\u043d \u043e\u0434\u043d\u043e\u0442\u0438\u043f\u043d\u043e\u0441\u0442\u044c\u044e \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442.<\/strong> \u041f\u0440\u0438 \u0431\u043e\u043b\u044c\u0448\u043e\u043c \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u043e\u0445\u043e\u0436\u0438\u0445 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043b\u0435\u0433\u043a\u043e \u0437\u0430\u043f\u0443\u0442\u0430\u0442\u044c\u0441\u044f \u0438 \u043f\u043e\u0442\u0440\u0430\u0442\u0438\u0442\u044c \u043b\u0438\u0448\u043d\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u043d\u0430 \u043f\u043e\u0438\u0441\u043a \u043d\u0443\u0436\u043d\u043e\u0439.<\/p>\n<\/li>\n<li>\n<p><strong>\u0414\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0432 \u043d\u0435\u0439\u043c\u0438\u043d\u0433\u0435<\/strong>. \u0412 \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u043c\u043e\u0433\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u044b \u0441 \u0441\u0443\u0444\u0444\u0438\u043a\u0441\u0430\u043c\u0438 PAGE, PATH, ROUTE \u0438 \u0442.\u043f. \u042d\u0442\u043e \u0443\u0441\u043b\u043e\u0436\u043d\u044f\u0435\u0442 \u0447\u0442\u0435\u043d\u0438\u0435 \u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u043a\u043e\u0434\u0430.<\/p>\n<\/li>\n<li>\n<p><strong>\u041e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u0441\u0432\u044f\u0437\u0438 \u043c\u0435\u0436\u0434\u0443 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438 \u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u043c\u0438<\/strong>. Path- \u0438 query-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u044b \u0438 \u0432 \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u0430\u0445 \u043f\u0443\u0442\u0435\u0439, \u0438 \u0432 \u0441\u0430\u043c\u0438\u0445 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446. \u0412 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u0434\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0442\u0438\u043f\u043e\u0432 \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0432\u0435\u0440\u043e\u044f\u0442\u043d\u043e\u0441\u0442\u044c \u043d\u0435\u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0439 \u0438 \u043e\u0448\u0438\u0431\u043e\u043a.<\/p>\n<\/li>\n<\/ol>\n<p>\u041a\u043e\u0433\u0434\u0430 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432 \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u0431\u043e\u043b\u044c\u0448\u0435, \u044d\u0442\u0438 \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043d\u0430\u043a\u0430\u043f\u043b\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0438 \u043d\u0435\u0433\u0430\u0442\u0438\u0432\u043d\u043e \u0432\u043b\u0438\u044f\u044e\u0442 \u043d\u0430 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u0443\u0435\u043c\u043e\u0441\u0442\u044c.<\/p>\n<h2>\u0411\u0430\u0437\u043e\u0432\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b<\/h2>\n<p>\u0412 \u0441\u0432\u043e\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u043c\u044b \u0440\u0435\u0448\u0438\u043b\u0438 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0435\u0434\u0438\u043d\u044b\u0439 \u043e\u0431\u044a\u0435\u043a\u0442, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043a\u043b\u044e\u0447\u0438 \u0438\u043c\u0435\u044e\u0442 \u043f\u043e\u043b\u043d\u043e\u0435 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435 \u0441\u0442\u0440\u043e\u043a\u043e\u0432\u043e\u043c\u0443 \u043f\u0443\u0442\u0438, \u043a\u0440\u043e\u043c\u0435 \u0434\u0432\u0443\u0445 \u0441\u043b\u0443\u0436\u0435\u0431\u043d\u044b\u0445:<\/p>\n<ul>\n<li>\n<p><strong>index<\/strong> \u0434\u043b\u044f \u043a\u043e\u0440\u043d\u0435\u0432\u044b\u0445 \u043f\u0443\u0442\u0435\u0439;<\/p>\n<\/li>\n<li>\n<p><strong>item<\/strong> \u0434\u043b\u044f \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u043f\u0443\u0442\u0435\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432, \u043e\u0431\u044b\u0447\u043d\u043e \u043f\u043e \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0430\u043c.<\/p>\n<\/li>\n<\/ul>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0432\u0441\u0435 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u0432 \u0442\u0430\u043a\u043e\u043c \u0432\u0438\u0434\u0435:<\/p>\n<pre><code class=\"typescript\">export const Routes = {   index: '\/',   login: '\/login',   notFound: '\/404',   search: '\/search',   tasks: {     index: '\/tasks',     actual: '\/tasks\/actual',     finished: '\/tasks\/finished',     item: (id: number, type: string) =&gt; `\/tasks\/${id}?type=${type}`   }, }<\/code><\/pre>\n<p>\u0410 \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044f \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"typescript\">const navigate = useNavigate(); ... navigate(Routes.tasks.item(123))<\/code><\/pre>\n<p><strong>\u042d\u0442\u043e \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u043e\u0435 \u0438 \u0431\u044b\u0441\u0442\u0440\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435, \u0438 \u0442\u0435\u043f\u0435\u0440\u044c \u0443 \u043d\u0430\u0441<\/strong>:<\/p>\n<ul>\n<li>\n<p>\u0435\u0434\u0438\u043d\u0430\u044f \u0442\u043e\u0447\u043a\u0430 \u0432\u044b\u0431\u043e\u0440\u0430 \u043f\u0443\u0442\u0438, \u0430 \u0430\u0432\u0442\u043e\u043a\u043e\u043c\u043f\u043b\u0438\u0442 IDE \u0431\u044b\u0441\u0442\u0440\u043e \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u043d\u0430\u0439\u0442\u0438 \u043d\u0443\u0436\u043d\u044b\u0439;<\/p>\n<\/li>\n<li>\n<p>\u0432\u0441\u0435 \u043f\u0443\u0442\u0438 \u0447\u0451\u0442\u043a\u043e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u044b, \u043b\u0435\u0433\u043a\u043e \u043e\u0440\u0438\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u043d\u043e\u0432\u044b\u0435;<\/p>\n<\/li>\n<li>\n<p>\u043d\u0435\u0442 \u0434\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043b\u0438\u0448\u043d\u0438\u0445 \u0441\u043b\u043e\u0432 \u0432 \u043d\u0435\u0439\u043c\u0438\u043d\u0433\u0435 \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442.<\/p>\n<\/li>\n<\/ul>\n<p>\u041d\u0435 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u044c \u0438\u043c\u0435\u043d\u043d\u043e \u043d\u0430\u0448\u0435\u0439 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0435: \u0441\u043e\u0431\u043b\u044e\u0434\u0430\u0442\u044c \u0442\u043e\u0447\u043d\u044b\u0439 \u043d\u0435\u0439\u043c\u0438\u043d\u0433 \u043a\u043b\u044e\u0447\u0435\u0439, \u044e\u0437\u0430\u0442\u044c \u0441\u043b\u0443\u0436\u0435\u0431\u043d\u044b\u0435 index\/item \u0438\u043b\u0438 \u0435\u0434\u0438\u043d\u044b\u0439 \u043e\u0431\u044a\u0435\u043a\u0442 \u2014 \u044d\u0442\u043e \u043b\u0438\u0448\u044c \u0434\u043e\u0433\u043e\u0432\u043e\u0440\u0451\u043d\u043d\u043e\u0441\u0442\u044c \u043d\u0430\u0448\u0438\u0445 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432. \u0413\u043b\u0430\u0432\u043d\u043e\u0435 \u2014 \u0432\u044b\u0431\u0440\u0430\u0442\u044c \u0443\u0434\u043e\u0431\u043d\u0443\u044e \u0438 \u043f\u043e\u043d\u044f\u0442\u043d\u0443\u044e \u0441\u0445\u0435\u043c\u0443 \u0434\u043b\u044f \u0432\u0430\u0448\u0435\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u044b.<\/p>\n<h2>\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u0443\u0435\u043c \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b<\/h2>\n<p>\u041d\u043e \u044d\u0442\u043e \u0431\u044b\u043b\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430\u0447\u0430\u043b\u043e. \u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u043c\u044b \u0440\u0435\u0448\u0438\u043b\u0438 \u0440\u0430\u0441\u043a\u0440\u044b\u0442\u044c \u043f\u043e\u0442\u0435\u043d\u0446\u0438\u0430\u043b TypeScript.<\/p>\n<figure class=\"full-width\"><\/figure>\n<p>\u0418\u0434\u0435\u044f \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u0441\u0432\u044f\u0437\u0430\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u0438 \u043e\u0431\u044a\u0435\u043a\u0442 \u043f\u0443\u0442\u0435\u0439 <strong>Routes<\/strong>.<\/p>\n<p>\u0415\u0441\u0442\u044c \u0434\u0432\u0430 \u0442\u0438\u043f\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432:<\/p>\n<ul>\n<li>\n<p><strong>Path<\/strong>: \/example\/:<strong>id<\/strong><\/p>\n<\/li>\n<li>\n<p><strong>Query<\/strong>: \/example?<strong>id<\/strong>=123<\/p>\n<\/li>\n<\/ul>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0441\u043f\u0435\u0440\u0432\u0430 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043c \u043f\u0440\u043e\u0442\u043e\u0442\u0438\u043f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445. \u0411\u044b\u043b\u043e \u0431\u044b \u043a\u043b\u0430\u0441\u0441\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0432 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u0443\u044e \u0443\u0442\u0438\u043b\u0438\u0442\u0443 \u043d\u0430\u0448 \u043f\u0443\u0442\u044c \u0438\u043b\u0438 \u0435\u0433\u043e \u0442\u0438\u043f \u0438\u0437 <strong>Routes<\/strong>, \u0430 \u0432 \u043e\u0442\u0432\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b. \u0422\u043e\u0433\u0434\u0430 \u0432\u0441\u0435 \u0442\u0438\u043f\u044b \u043c\u044b \u0441\u043c\u043e\u0433\u043b\u0438 \u0431\u044b \u043e\u043f\u0438\u0441\u0430\u0442\u044c \u0435\u0434\u0438\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u043e \u0432 <strong>Routes<\/strong>. \u041d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 React-\u0445\u0443\u043a\u0430:<\/p>\n<pre><code class=\"typescript\">const { id, type } = usePageParams&lt;typeof Routes.tasks.item&gt;();<\/code><\/pre>\n<p><strong>\u0420\u0430\u0437\u0431\u0435\u0440\u0435\u043c \u043f\u043e\u0448\u0430\u0433\u043e\u0432\u043e, \u043a\u0430\u043a \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f:<\/strong><\/p>\n<p>1. \u0421\u043f\u0435\u0440\u0432\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u0443\u0435\u043c \u043f\u0443\u0442\u0438 \u0432 Routes.<\/p>\n<p>\u0414\u043b\u044f \u043f\u0440\u043e\u0431\u0440\u043e\u0441\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0442\u0430\u043a\u043e\u0433\u043e \u0432\u0438\u0434\u0430:<\/p>\n<pre><code class=\"typescript\">export const Routes = {   ...   tasks: {     item: (id: number, type: string) =&gt; `\/tasks\/${id}?type=${type}`   } }<\/code><\/pre>\n<p>\u0410 \u0447\u0442\u043e\u0431\u044b \u0443\u043f\u0440\u043e\u0441\u0442\u0438\u0442\u044c \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044e \u0442\u0430\u043a\u0438\u0445 \u0444\u0443\u043d\u043a\u0446\u0438\u0439, \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u0443\u044e \u0443\u0442\u0438\u043b\u0438\u0442\u0443, \u043a\u043e\u0442\u043e\u0440\u0430\u044f:<\/p>\n<ul>\n<li>\n<p>\u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u043f\u0443\u0442\u044c \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u0430 (\u0435\u0441\u043b\u0438 path-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u043d\u0435\u0442 \u2014 \u0441\u0442\u0440\u043e\u043a\u0443, \u0430 \u0435\u0441\u043b\u0438 \u0435\u0441\u0442\u044c \u2014 \u0444\u0443\u043d\u043a\u0446\u0438\u044e).<\/p>\n<\/li>\n<li>\n<p>\u0431\u0443\u0434\u0435\u0442 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u043f\u0443\u0442\u0438 \u0441 \u0432\u043d\u0435\u0434\u0440\u0435\u043d\u0438\u0435\u043c path \u0438 query-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432:<\/p>\n<\/li>\n<\/ul>\n<p>\u0422\u0430\u043a\u0436\u0435 \u0434\u043b\u044f \u0443\u0434\u043e\u0431\u0441\u0442\u0432\u0430 \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u043c <strong>path<\/strong> \u0438 <strong>query<\/strong> \u043d\u0430 \u0440\u0430\u0437\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0438 \u0432\u0432\u0435\u0434\u0451\u043c \u043f\u0440\u043e\u0441\u0442\u0435\u043d\u044c\u043a\u0438\u0439 \u0445\u0435\u043b\u043f\u0435\u0440 <strong>createUrl<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0441\u043a\u043b\u0435\u0438\u0432\u0430\u0442\u044c \u043e\u0440\u0438\u0434\u0436\u0438\u043d, \u043f\u0443\u0442\u044c, \u043a\u0432\u0435\u0440\u0438 \u0438 \u0445\u044d\u0448 \u0432 \u0438\u0442\u043e\u0433\u043e\u0432\u044b\u0439 URL.<\/p>\n<pre><code class=\"typescript\">import { stringify } from 'qs';  export type TParametrizedRoute&lt;TPath = any, TQuery = any&gt; = (params: {   path?: TPath;   query?: TQuery; }) =&gt; string;  \/\/ \u0423\u0442\u0438\u043b\u0438\u0442\u0430 \u0433\u0435\u043d\u0435\u0440\u0430\u0442\u043e\u0440 \u0444\u0443\u043d\u043a\u0446\u0438\u0439: \/\/ - \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u043c \u043f\u0443\u0442\u044c \u0432 \u0432\u0438\u0434\u0435 \u0441\u0442\u0440\u043e\u043a\u0438 \u0438\u043b\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \/\/ - \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u0438\u0440\u0443\u0435\u0442 \u043c\u0430\u0440\u0448\u0440\u0443\u0442 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438 function parametrizedRoute&lt;TPath = undefined, TQuery = undefined&gt;(   pathOrGetPath: TPath extends undefined ? string : (path: TPath) =&gt; string ): TParametrizedRoute&lt;TPath, TQuery&gt; {   return ({ path, query }: { path?: TPath; query?: TQuery }) =&gt; {     return createUrl({       path: typeof pathOrGetPath === 'function'               ? pathOrGetPath(path!)               : pathOrGetPath || '',       query     });   }; }  \/\/ \u0412 \u0441\u0442\u0430\u0442\u044c\u0435 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u043f\u0440\u0438\u043c\u0438\u0442\u0438\u0432\u043d\u0430\u044f \u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u0430\u044f \u0443\u0442\u0438\u043b\u0438\u0442\u0430 \u0434\u043b\u044f \u0441\u043a\u043b\u0435\u0439\u043a\u0438 \u0447\u0430\u0441\u0442\u0435\u0439 URL\u0430. \/\/ \u041f\u0440\u0438\u043c\u0435\u0440 \u0433\u043e\u0442\u043e\u0432\u043e\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438: https:\/\/github.com\/meabed\/build-url-ts export const createUrl = (args: {   origin?: string;   path?: string;   query?: Record&lt;string, unknown&gt;;   hash?: string; }): string =&gt; {   return [     args.origin || '',     args.path || '',     args.query ? `?${stringify(args.query)}` : '',     args.hash ? `#${args.hash}` : ''   ].join(''); };<\/code><\/pre>\n<p>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043d\u043e\u0432\u0443\u044e \u0443\u0442\u0438\u043b\u0438\u0442\u0443 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043f\u0443\u0442\u0435\u0439, \u0435\u0441\u0442\u044c \u0447\u0435\u0442\u044b\u0440\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f:<\/p>\n<pre><code class=\"typescript\">export const Routes = {   tasks: {     \/\/ \u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0435\u0441\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e query - \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u043c \u0441\u0442\u0440\u043e\u043a\u0443 \u043f\u0443\u0442\u0438:     index: parametrizedRoute&lt;undefined, { userId?: number; type?: string }&gt;(       '\/tasks'     )      \/\/ \u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0435\u0441\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e path:     item: parametrizedRoute&lt;{ id: number }&gt;((params) =&gt; {       return `\/tasks\/${params.id}`;     }),      \/\/ \u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0435\u0441\u0442\u044c \u0438 path, \u0438 query:     item: parametrizedRoute&lt;{ id: number }, { type?: string }&gt;((params) =&gt; {       return `\/tasks\/${params.id}`;     }),      \/\/ \u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043d\u0435\u0442 \u043d\u0438 query, \u043d\u0438 path:     list: '\/tasks\/list'   } };<\/code><\/pre>\n<p>2. \u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u0442\u0438\u043f\u044b \u0434\u043b\u044f \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432:<\/p>\n<pre><code class=\"typescript\">\/\/ \u0422\u0438\u043f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u0443\u0442\u0438 \u0443\u0436\u0435 \u0432\u0432\u0435\u043b\u0438 \u0440\u0430\u043d\u0435\u0435:  export type TParametrizedRoute&lt;TPath = any, TQuery = any&gt; = (params: {   path?: TPath;   query?: TQuery; }) =&gt; string;  \/\/ \u041f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u0432\u0441\u0435 \u043a\u043e\u043d\u0435\u0447\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0432 URL'\u0435 \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u044f\u0442\u0441\u044f \u0432 \u0441\u0442\u0440\u043e\u043a\u0438, \u0442\u043e \u043b\u0443\u0447\u0448\u0435 \u043f\u0440\u0438\u0432\u0435\u0441\u0442\u0438 \u043a \u043d\u0438\u043c, \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 \u0442\u0438\u043f-\u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0435\u0440: export type TObjectFieldsToStrings&lt;T&gt; = {   [K in keyof T]: T[K] extends string | undefined ? T[K] : string; };   \/\/ \u0423\u0442\u0438\u043b\u0438\u0442\u0430 \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u0442 \u0438 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u0443\u0435\u0442 \u043d\u0443\u0436\u043d\u043e\u0435 \u043f\u043e\u043b\u0435 \u0438\u0437 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u0443\u0442\u0438 type TExtractRouteParams&lt;   T extends TParametrizedRoute,   K extends keyof Parameters&lt;T&gt;[0] &gt; = TObjectFieldsToStrings&lt;   NonNullable&lt;Parameters&lt;T&gt;[0][K]&gt; &gt;;  \/\/ \u0423\u0442\u0438\u043b\u0438\u0442\u0430 \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 path \u0438\u0437 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u0443\u0442\u0438 export type TExtractPageParams&lt;T extends TParametrizedRoute&gt;   = TExtractRouteParams&lt;T, 'path'&gt;;  \/\/ \u0423\u0442\u0438\u043b\u0438\u0442\u0430 \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 query \u0438\u0437 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u0443\u0442\u0438 export type TExtractPageQuery&lt;T extends TParametrizedRoute&gt;   = TExtractRouteParams&lt;T, 'query'&gt;;<\/code><\/pre>\n<p>3. \u0418 \u0443\u0442\u0438\u043b\u0438\u0442\u044b \u0434\u043b\u044f \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432:<\/p>\n<pre><code class=\"typescript\">import { stringify } from 'qs'; import { useCallback, useMemo } from 'react'; import { useParams } from 'react-router'; import { URLSearchParamsInit, useSearchParams } from 'react-router-dom';  \/**  * \u0423\u0442\u0438\u043b\u0438\u0442\u0430 \u0434\u043b\u044f \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b (\u0438\u0437 path + \u0438\u0437 query)  *\/ export function usePageParams&lt;T extends TParametrizedRoute&gt;(): {   path: TExtractPageParams&lt;T&gt;;   query: Partial&lt;TExtractPageQuery&lt;T&gt;&gt;;   setParams: ReturnType&lt;typeof useQueryParams&lt;TExtractPageQuery&lt;T&gt;&gt;&gt;[1]; } {   const path = useParams() as TExtractPageParams&lt;T&gt;;   const [query, setParams] = useQueryParams&lt;TExtractPageQuery&lt;T&gt;&gt;();    return { path, query, setParams }; }   \/**  * \u0423\u0442\u0438\u043b\u0438\u0442\u0430 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 query-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438, \u0435\u0441\u043b\u0438 \u0435\u0451 \u043d\u0435\u0442 \u0432\u043e \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0435  *\/ export function useQueryParams&lt;T = Record&lt;string, unknown&gt;&gt;(   defaultValues?: URLSearchParamsInit ): [Partial&lt;T&gt;, (values: Partial&lt;T&gt;, replace?: boolean) =&gt; void] {   const [value, setValue] = useSearchParams(defaultValues);    const parsedQueryParams = useMemo(     () =&gt; Object.fromEntries(value.entries()) as Partial&lt;T&gt;,     [value]   );    const setQueryParams = useCallback(     (values: Partial&lt;T&gt;, replace: boolean = true) =&gt; {       setValue(stringify({ ...parsedQueryParams, ...values }), { replace });     },     [parsedQueryParams]   );    return [parsedQueryParams, setQueryParams]; }<\/code><\/pre>\n<p>4. \u0418\u0442\u043e\u0433\u043e\u0432\u043e\u0435 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0442\u0430\u043a:<\/p>\n<pre><code class=\"typescript\">export const TaskPage = (): ReactElement =&gt; {   const params = usePageParams&lt;typeof Routes.tasks.item&gt;();   const { path: { id }, query: { type } } = params;    return ( ... ); }<\/code><\/pre>\n<p>\u0413\u043e\u0442\u043e\u0432\u043e! \u0410 \u0430\u0432\u0442\u043e\u043a\u043e\u043c\u043f\u043b\u0438\u0442 \u0432 IDE \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u043f\u043e\u0434\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043f\u0440\u0438 \u0438\u0445 \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u0438.<\/p>\n<h2>\u0423\u043b\u0443\u0447\u0448\u0430\u0435\u043c \u0441\u0445\u0435\u043c\u0443: \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0438\u0437 \u0441\u0442\u0440\u043e\u043a<\/h2>\n<p>\u0410 \u0447\u0442\u043e, \u0435\u0441\u043b\u0438 \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u0440\u0443\u0447\u043d\u043e\u0433\u043e \u0443\u043a\u0430\u0437\u0430\u043d\u0438\u044f path-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432? \u0412\u0435\u0434\u044c \u043e\u0431\u044b\u0447\u043d\u043e \u043e\u043d\u0438 \u0443\u0436\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u0441\u044f \u0432 \u043f\u0443\u0442\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b. \u0418 \u044d\u0442\u043e\u0442 \u0436\u0435 \u0441\u0442\u0440\u043e\u043a\u043e\u0432\u044b\u0439 \u043f\u0443\u0442\u044c \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u0442\u0441\u044f \u0432 Router:<\/p>\n<pre><code class=\"typescript\">{   path: '\/tasks\/:id',   element: &lt;TaskPage \/&gt; }<\/code><\/pre>\n<p>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u0443\u043f\u0440\u043e\u0449\u0451\u043d\u043d\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e. \u041c\u044b \u0445\u043e\u0442\u0438\u043c \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-465891","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/465891","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=465891"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/465891\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=465891"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=465891"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=465891"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}