Правильные page layouts во Vue
HTML-код
- Опубликовано: 5 авг 2024
- Видео посвящено анализу и разработке системы page layouts во Vue 3
⌛ Таймкоды ⌛
0:00 - Введение
0:10 - Обзор видео
1:01 - Что такое шаблоны?
2:17 - Требования к системе шаблонов
3:41 - ПРОСТО СЛОТЫ обзор подхода
4:19 - ПРОСТО СЛОТЫ реализация подхода
4:58 - ПРОСТО СЛОТЫ демонстрация, проблемы
6:18 - ВОТЧЕРЫ, КОМПЬЮТЕДЫ обзор подхода
8:00 - ВОТЧЕРЫ, КОМПЬЮТЕДЫ реализация подхода
9:12 - ВОТЧЕРЫ, КОМПЬЮТЕДЫ демонстрация, проблемы
12:04 - МИДЛВАРКА обзор подхода
13:01 - МИДЛВАРКА реализация подхода
14:13 - МИДЛВАРКА демонстрация
16:36 - Проверка выполнения требований
17:47 - Выводы
⚡ Ссылки ⚡
Презентация - slides.com/sticker0ne/vue-lay...
Репа гитлаба - gitlab.com/Sticker0ne/vue-lay...
Статья Manu - itnext.io/vue-tricks-smart-la...
Статья Edward - habr.com/ru/post/513728/
✨ Так же я и разработка ✨
Мой канал в телеграмме - t.me/developers_workshop
Мой инстаграм - / stick_one
#layouts #vue #typescript #vuejs #javascript #browser #layout #page #react #it #vite #development #webdev #web #js Наука
Как стать таким же умным разработчиком?
Дядька реально умни)
Сергей, большое спасибо вам за данное видео, оно прекрасно!
Показана проблема, показаны разные варианты решения, их плюсы и минусы, разобрана реализация каждого подхода, идеально!
Жаль, что вы делаете так мало контента, у вас определено талант!
Большое спасибо, мне очень приятно!)
Проблемы с распределением времени, нужны помощники в монтаже)
Полезная информация и хорошая подача, спасибо! Буду ждать новых видосов 🙃
Это шикарно! Спасибо Сергей!
Очень приятно, спасибо!)
Интересненько!!! Спасибо, тебе, добрый человек!
Рад, что понравилось!
Отличный и очень для меня актуальный видосик :) Огромная благодарность автору☺
Спасибо, большое! Очень приятно читать такие комментарии!
Зачет, спасибо!!!! На работе попробую))
Пожалуйста:) Отпиши как зайдет)
Спасибо огромное - очень полезное видео!
Очень приятно, спасибо!
Спасибо за видео) Подписался, надеюсь будут выходить такие ролики почаще)
Спасибо, за комментарий!) Подписывайся на телеграм канал, там переодически публикуется что-нибудь интересное по теме :) Я стараюсь выпускать видео чаще, но пока что получается не очень
@@dev-workshop Уже подписался)
Респект, очень классное видео!
Спасибо, очень рад!)
Шикарно!! Спасибо
Пожалуйста)
Хороший выпуск! Серег, очень жду разбор аккордиона!
Разбор аккордеона, так и запишем
Топич, спасибо большое за ролик
Рад, что понравилось)
ты лучший ❤
Спасибо, интересно, получил задачу реализовать динамические роуты на vue в зависимости от роли, которая приходит slug’ом от сервера, завтра попробую применить подобное, до этого все время на react проекты были :) на проекте вроде есть какая-то middleware, но я ещё детально не ковырял.
Рад, что видео понравилось!) По твоей формулировке, мне кажется, это называется security middleware или authorization guard, что-то подобное. Вот здесь есть абстрактный пример (третий сверху) router.vuejs.org/guide/advanced/navigation-guards.html#global-before-guards
@@dev-workshop по факту все оказалось проще, чем говорили. Layout все таки один, а меню, хедер и футер генерируют разное содержимое через store сами, я сделал по принципу как в примере из ссылки, только вешал на нужные роуты и через meta задавал массив ролей разрешённых.
супер!
Спасибо:)
Спасибо автору! Круто когда люди делятся открытиями с обществом) Мне интересна тема для следующего видео) если конечно вопрос стоящий: насколько успешно поисковик считывает контент с SPA приложений и как лучше подавать контент для поисковика в vuejs?
Привет! Спасибо!)
Практически не на сколько. Хоть все и говорят, что вот вот индексаторы научатся в спа - это все не правда
Все, что должно попасть в индексацию должно быть ssr
Не пойму к чему такие сложности. Можно же проще написать и принцип mount/unmount лайота с компонентами такой же будет:
в App.vue
в router.js (все компоненты пихаем в children нужного layout)
const routes = [
{
path: '/',
name: 'baselayout',
component: BaseLayout,
meta:{
layout: BaseLayout
},
children: [
{
path: '/page1/',
name: 'page1',
component: () => import('@/views/Page1.vue')
},
{
path: '/page2',
name: 'page2',
component: () => import('@/views/Page2.vue')
},
]
},
{
path: '/guard',
name: 'guard',
component: () => import('@/views/guard/GuardIndex.vue'),
meta:{
layout: AdminLayout
},
children: [
{
path: '/page3.',
name: 'page3',
component: () => import('@/views/Page2.vue')
},
]
},
];
в BaseLayout.vue (transition конечно здесь лишний. просто допом оставил)
И все работает и при хождении по дочерним вьюшкам layout не размонтируется каждый раз, а только тогда пока перехожу на дочернюю вьюшку другого layout. Глюков с переходами при таком подходе пока не увидел.
Автор, поправьте, если что-то не так с таким подходом, так как не очень хорошо шарю за vue. Спасибо за видео
привет, а почему не работает с алиасом в мидлваре, если использую @ в пути функции import, то не работает?
Привет! Алиасы резолвятся веб паком или витом, в мидлварках не резолвятся, не копал почему. Так же в nuxt.config, например, не резолвятся. Нужно копат
а есть такое эффект при использовании этой системы, сейчас опищу: в футоре сайта ссылка на страницу политики, соответственно я проскролил страницу далеко в низ. так вот при клике на ссылку я меняется страница и я остаюсь так же проскроденный внизу, как это исправить, чтобы новая страница открывалась с начала а не с того места где я был проскролен до клика по ссылке ??
Вариантов много, от кастомных мидлварок, до обратчиков в маунте странички. Но мне кажется, самый правильный вот этот router.vuejs.org/guide/advanced/scroll-behavior.html
Вопрос (оффтоп): у меня есть layout (шапка и футер) В котором есть компонент (страница с данными). В шапке тоже есть свои данные, все прекрасно работает. Но я хочу передать некоторые данные из чайлд компонента в layout, дабы отобразить инфу про страницу в layout. Не хочу лезть в vuex. Как можно передать данные из чайлда в layout?
Это плохая практика, лучше делать это каким-то отдельным инструментом.
Но как вариант, можно воспользоваться либо ивентбасом
Либо сделать синглтон композобл и воспользоваться им в шапке и на странице. Но это плохая практика, столкнетесь со многими проблемами :)
@@dev-workshop ок спасибо
Пытался, что-то подобное у себя написать не получилось. Может оформишь в виде плагина или дашь консультацию?
С плагином сложно, так как много специфики на каждом проекте, я лишь продемонстрировал общую идею. В видео есть ссылка на гитлаб, там рабочий проект его можно склонировать к себе и поковырять. Что именно не получается?
а почему в последнем подходе сперва маунтится шаблон страницы, а потом после него шаблон леяута, если страница - это дочерний компонент леяута?
Потому что сначала маунтятся самые глубокие дети и потом всплывает наверх)
medium.com/@brockreece/vue-parent-and-child-lifecycle-hooks-5d6236bd561f
С компютедом работает, причем все правильно, я буквально немного переписал и все отрабатывает как должно быть, вот лог из консоли, сначала зашел на дефолтную страницу с дефолт лейаутом и потом перешел на другую страницу с другим лейаутом. Если что версия vue - 2. LOG:
HomeView has been mounted
DefaultLayout has been mounted
DefaultLayout has been destroyed
HomeView has been destroyed
ErrorView has been mounted
ErrorLayout has been mounted
Да, на 2 вью работает, я согласен. На 3 начинаются пляски :)
А что именно ты переписал?
Скажите , как в функцию loadLayoutMiddleware(route) попадет route, мы же const route = useRoute() не делали🤔
Так работают мидлварки, роутер сам передает роут в каждую мидлварку и ожидает каких-то манипуляций с ним и вызывает мидлварки по цепочке :)
А еще функция useRoute может быть вызвана только топ левел в файлах компонентов
@@dev-workshop Спасибо! :)
@@user-nc6gt7gb1x Пожалуйста :)
С этим лайоут системой столкнулся на такое когда меняется хэш (window.location.hash) загружает снова лейоут и страница фризится
Надо смотреть, что в этот момент происходит с роутером вьюшным
@@dev-workshop Страница состоит из двух блоков ЛЕВАЯ часть просто список линков по клику вызывается функция которая ищет в DOM element(ПРАВАЯ часть) если находит делает scroll ПРАВУЮ часть и потом добавляет в адрес localtion.hash = '#somehash' (пробывал и $router.push({hash:''})
Привет! Пытаюсь применить указанный 3-ий способ с воем проекте.
Тут TS ругается на "route.meta.layoutComponent":
Type VueElement | undefined is not assignable to type string | ComponentDefinition
Буду благодарен за комментарий.)
Привет!
А ты не забыл?
declare module "vue-router" {
interface RouteMeta {
layout?: AppLayoutsEnum;
layoutComponent?: VueElement;
}
}
на сколько обоснован динамичесикй импорт? прочему не обычный?
на столько же, на сколько обоснован code splitting
Привет, Объясни пожалуйста, почему дефолтный метод обработки лайоутов через vue-router не подходит?
Привет! А что ты подразумеваешь под дефолтным?
@@dev-workshop когда в роутах указываешь лейоут и его childrens;
```
{
path: "/",
name: "ALayout",
component: () => import("../layouts/A/index.vue"),
children: [
{
path: "/one",
name: "OnePage",
component: () => import(../views/OnePage.vue"),
},
{
path: "/two",
name: "OnePage",
component: () => import(../views/TwoPage.vue"),
},
]
},
{
path: "/",
name: "BLayout",
component: () => import("../layouts/B/index.vue"),
children: [
{
path: "/three",
name: "ThreePage",
component: () => import(../views/ThreePage.vue"),
},
{
path: "/four",
name: "FourPage",
component: () => import(../views/FourPage.vue"),
},
]
}
```
@@user-vb6xh6yj4h В первые сталкиваюсь с таким подходом, если честно :) Так как я с ним не работал могу только поверхностно его проанализировать.
Во-первых на мой взгляд использование дочерних роутов именно для лэйаутов не совсем подходящий инструмент - субъективщина
Во-вторых, будет создан два раза роут с путем "/", хотя по идее, его быть не должно, если мы все страницы раскидали.
В третьих, у неопытных разработчиков могут возникнуть вопросы относительно того, как именно будет резолвится этот несчастный роут "/"
В четвертых, а как мне разместить страницу с выбранным лэйаутом по пути "/"?
В пятых, что делать, если я хочу по роуту "/product/item" иметь лэйаут "main", а по роуту /product/item/actions лэйаут "productActionsLayout"? Ну и тому подобное по вложенным маршрутам с разными лэйаутами.
В целом, вышеперечисленное мной - скорее придирки, основанные на поверхностном анализе, нужно смотреть глубже и ковырять.
Я еще нашел способ, кажется он еще проще моего, использовать named-view v3.router.vuejs.org/guide/essentials/named-views.html#nested-named-views
но вопрос, будет ли корректно работать, если один компонент, будет вложен в другой
@@user-vb6xh6yj4h В первые сталкиваюсь с таким подходом, если честно :) Так как я с ним не работал могу только поверхностно его проанализировать.
Во-первых на мой взгляд использование дочерних роутов именно для лэйаутов не совсем подходящий инструмент - субъективщина
Во-вторых, будет создан два раза роут с путем "/", хотя по идее, его быть не должно, если мы все страницы раскидали.
В третьих, у неопытных разработчиков могут возникнуть вопросы относительно того, как именно будет резолвится этот несчастный роут "/"
В четвертых, а как мне разместить страницу с выбранным лэйаутом по пути "/"?
В пятых, что делать, если я хочу по роуту "/product/item" иметь лэйаут "main", а по роуту /product/item/actions лэйаут "productActionsLayout"? Ну и тому подобное по вложенным маршрутам с разными лэйаутами.
В целом, вышеперечисленное мной - скорее придирки, основанные на поверхностном анализе, нужно смотреть глубже и ковырять.
Я еще нашел способ, кажется он еще проще моего, использовать named-view v3.router.vuejs.org/guide/essentials/named-views.html#nested-named-views
но вопрос, будет ли корректно работать, если один компонент, будет вложен в другой
@@dev-workshop поизучайте подробнее, потом поделитесь, роут выполняет все поставленные требования=))
Есть лайот шапка внутри инпут с выпадающию меню и у каждой страницы выпадающию меню свой. Как реализовать такое отношение?
либо через внешний стор, либо сделать композабл с замыканием состояния и на каждой странице обновлять данные для выпадашки
@@dev-workshop замыкание состояние страницы?
@@HalauLilau нет, пишешь замыкание внутри которого используешь, грубый пример
const dropdownData = useRef();
export function useLayoutDropdown() {
function updateData(newValue) {
dropdownData.value = newValue;
}
return reactive( {
dropdownData,
updateData
})
}
затем в лейауте вызываешь useLayoutDropdown и используешь из него данные и на страницы используешь useLayoutDropdown и обновляешь данные.
Но это очень грубый пример, здесь получается синглтон, который нельзя использовать, если у тебя ssr
Но никто не мешает создать такое замыкание в контексте запроса
@@HalauLilau еще можно рулить данными для выпадашки на уровне роутера
@@dev-workshop у меня ssr нет. Оказывается не только данные и иинпуты и селекты разные. Например страница клиент там в дропдауне фильтр поле дата, город, номер. А проектах другие поля в дродауне
Почему не использовать двухуровневый router? Два RouterVue, один для layout-ов, второй для page-ов. И все ... никаких лишних маунтов. Просто надо настроить правильно роутер...
Не рассматривал такой вариант. Надо подумать
Согласен, я использую двyxypoвнeвый роутер. В шаблоне layout компонентов достаточно добавить router-view в нужное место и сделать сборщик роутинга.
Минус подхода:
- Нужны префиксы в урлах, типо /panel /login либо смириться что на корневой url будет 1 layout
- Нужно один раз написать инфраструктурный код который будет собирать роутинг и класть страницу в childs с нужным layout.
Плюсы:
- Так как страницы префиксные, легко добавить глобальный permissions guards по типу user должен быть авторизован.
- Не нужно в описаниях новых страниц тащить импорты на layout
- Нет middleware’s
- Динамический импорт работают нативно через функцию в качестве параметра component.
- Можно натравить webpack чтобы он собирал несколько страниц в один chunk
- Конфиг страницы прост до ужаса, url, component, meta в котором прописано название layout
@@a_mensky а можешь скинуть минимальную демку?
@@dev-workshop ссылку постил, куда то пропадает
@@a_mensky привет, а можешь в телегу скинуть где почитать об этом методе? flexsoxo
Вопрос, при такой системе лейаутов, если обновлять страницу то прокрутка страницы возвращается в самое начало. В других проектах, если ты прокрутил допустим до середины, то после F5 страница туда же возвратится. Как можно исправить?
@@mster_r воспользоваться другими системами лейаутов, про которые вы написали?)
@@dev-workshop но мне нравится эта система, да и нет других на примете) Я имел ввиду обычное поведение браузера, под другими проектами. Просто неожиданное неудобство такое тут возникло, думал может известно решение. Или может подскажешь как бы решил, в какую сторону подумать?
@@dev-workshop А может вообще я сам где напортачил, и проблема не от лейаутов. Встречалось ли тебе такое?
@@mster_r все очень индивидуально, нужно смотреть проект и его код)
всегда можно что-нибудь придумать, например хранить в sessionStorage последнее значение позиции скролла и потом при маунте заставлять браузер скролиться на эту позицию)
@@dev-workshop понял, спасибо, тоже думал в эту сторону. Для меня показательно уже то, что с проблемой скроллинга столкнулся только я)
Я мб не внимательно смотрел, а чем не устраивает стандартная реализация vue-router ?
```
{
path: '/',
component: LayoutDefault,
name: 'Layout Default',
children: [
{ path: '/', component: ViewHome, name: 'Home' },
{ path: '/example/:id', component: ViewExample, name: 'Example' },
]
},
```
А что если мы внутри лейаута ходим на сервер и хотим чтоб дочерний компонент не маунтился, пока он туда не сходит? В таком случае этот подход не совсем работает или я ошибаюсь?
Я думаю, что подходит, как раз таки потому, что маунт лэйаута, при таком подходе вызовется один раз.
Точно сработает если ходить на сервер будет AppLayoutDefault или AppLayoutLogin
А вообще, я делаю все запросы на сервер, которые "общие" в отдельной мидлварке, ее можно сделать блокирующей рендер или не блокирующей
@@dev-workshop на тему общих запросов в мидлварке было бы классно видео от вас увидеть, если там есть что рассказать
@@xeleos Привет!) А что подразумевается под общими запросами?
@@dev-workshop вы же сами комментарием выше упомянули об этом. На самом деле осведомлённость документацией у вас хорошая , лично я, благодаря вам узнал о возможности их(мидлвар.) использовать в принципе. Благодарю за ликбез.
@@wwiiktor Очень рад!) Да иногда забываю о чем были комментарии в ранних видео. Если речь про запросы, которые общие для всех страниц (пользователь, пермишены, общая инфа и тд), то в принципе, можно записать видос, конечно)
красавчик / как раз увидел эту проблему двойного рендера при переключении лэйаута и искал решение
Спасибо) Рад, что твоя проблема решена :)
@@dev-workshop спасибо 👍
@@dev-workshop если не сложно подскажите пожалуйста, для работы с данными , например юзер залогинился, использовать vuex ?
@@hellocsgo3600 Если мы говорим про 3 вью - то лучше pinia. Но можно жить вообще без state manager'a
@@dev-workshop я vue 3 учу, спасибо посмотрю pina
Привет. Я нашёл баг который не смог сам исправить и запостил его тебе на гитлаб репо этого проекта. Посмотри пожалуйста.
Привет! Хорошо, гляну. Спасибо!
Это не баг, это желание использовать данный механизм иначе. В твоем случае стоит сделать несколько иначе, я отписал там в ишью
А в чём суть канала ? Разбор решений ?
Суть канала, на текущий момент - поделиться некоторым опытом в веб разработке, показать решение некоторых проблем. Продемонстрировать альтернативные подходы к решению задач.
Когда я сталкиваюсь с какими-либо проблемами в разработке или нахожу их в чужих проектах, иногда я придумываю оригинальные (на мой взгляд) решения и делюсь этими решениями.
Возможно, суть канала изменится со временем, хочется делать информативный контент для разработчиков
Как-то сложно
Использую children-компонент в роутере
{
path: "/users",
name: "usersLayout",
component: MainLayout,
children: [
{
path: "",
name: "users",
component: UsersPage,
},
],
},
Здесь MainLayout это основной шаблон, usersLayout просто название роута
На вкус и цвет. Сейчас не могу точно сказать, но по-моему у такого варианта есть проблемы с асинхронными импортами и как следствие - код сплиттингом, но могу ошибаться.
Ну и да, на вкус и цвет, как говориться
ИМХО:
разве это проще?
Как конечный разработчик, который будет переодически добавлять новые роуты, мне не очень нравится описание роута.
свойство children описывает вложенные роуты. В Вашем примере, почти всегда будет вложенный роут "по-умолчанию". А является ли он действительно вложенным, или он нужен только для реализации лэйаута?
А что с дефолтным лейаутом?
А будет ли ререндериться "MainLayout" при переходе между Роутами первого уровня?
А нужно ли разработчику думать в чем разница между $router.push({name: "users"}) и
$router.push({name: "usersLayout"}) ?
Выполнив $router.push({name: "usersLayout"}) какое имя будет у конечного роута? произойдет ли автоматический переход на роут с именем "users"? будут ли существовать два роута с разными именами но с одинаковым отображением?
Все эти вопросы возникают у меня, глядя на Ваш пример. При этом я не помню ответы на них, мне нужно идти и проверять.
Ну и кажется Вы упустили тот момент, что я делал решение, которое близко по АПИ к наксту, о чем я говорил в начале видео