Привет! Меня зовут Наташа Калачева. Я Frontend-разработчик в компании AGIMA. Vue — один из самых популярных фреймворков JS, его используют для разработки SPA и PWA. А его главные плюсы — это понятная, четкая документация, готовая структура и низкий порог входа.

Тем не менее, Frontend сегодня — это сложные приложения, которые содержат не только красивые элементы интерфейса, но и большую часть логики и функциональности всего продукта. Это требует от нас тщательного планирования и организации проекта, чтобы сделать его масштабируемым и простым.

В этой статье поделюсь правилами, которых придерживаюсь в работе и которые помогают упростить поддержку и расширение приложения. Мы рассмотрим, как организовать хранение компонентов, стилей и плагинов, когда использовать стор и полезные функции Vue.

Следуя этим рекомендациям, вы сможете создавать более эффективные проекты.

1. Делать простую масштабируемую структуру

Базовая структура папок

Когда проект разрастается, бывает сложно добавлять новые функции или поддерживать старый код. Вот почему необходимо подумать о правильной организации структуры папок, сделать ее удобной для использования и сэкономить время на процессе адаптации новых разработчиков.

После инициализации приложения с Vue CLI мы уже видим предложенную структуру. 

Assets: здесь организуем хранение файлов CSS, шрифтов и изображений.

Components: это автономные компоненты Vue, которые одновременно инкапсулируют структуру шаблона, логику JavaScript и представление CSS. 

Router: хранит все настройки роутинга и маршруты.

Store: содержит конфиг и данные хранилища (Vuex, Pinia).

Хорошая практика — придерживаться уже готового решения и расширять по мере необходимости. Хотя Vue не дает строгих рамок и мы можем менять файловую структуру как хотим, всё же использование знакомых стандартов делает проект более предсказуемым и простым.

Расширение базовой структуры

Это хороший старт, но в больших приложениях мы неизбежно расширяем эту структуру. Лучше сделать это в начале разработки, а не пытаться изменить, когда дедлайн близко. О чем же нужно задуматься на берегу?

Первое — это компоненты и принципы их разделения. Всё приложение состоит из набора компонентов. Если хранить их в одной папке, мы вскоре столкнемся с огромным количеством неструктурированного контента.

На какие папки можно разделить компоненты?

Components (ui). Здесь хранятся компоненты дизайн-системы. Это самые простые элементы интерфейса, которые часто переиспользуются. Обычно эти компоненты взаимодействуют с «внешним миром» через пропсы и события. Обращение из них к стору и роутингу будет лишним. Чаще всего они не отправляют запросов к серверу и не содержат сложной бизнес-логики.

Примеры таких компонентов: инпуты, кнопки, алерты и другие UI-элементы.

Blocks. Это компоненты блоков. Блок — небольшой кусок интерфейса, который состоит из компонентов и уже имеет бизнес-логику. Примером блока может служить карточка продукта. Важно также хранить блоки простыми, не обращаться из них к стору, к роуту, не делать лишних запросов. Чаще всего таким компонентам достаточно информации из пропсов. Это позволит переиспользовать один блок для нескольких страниц.

Views/pages. Страницы собираются из блоков и компонентов, но сами по себе являются более сложными компонентами, из которых мы обращаемся к стору, роутингу и т. д.

Layouts. Хранит компоненты-макеты с данными, которые используются для нескольких страниц. На нем обычно присутствует Footer, Header, глобальный прелоадер и др. Например, может быть один макет для авторизованных пользователей, другой — для страницы авторизации.

Помимо компонентов, важно организовать хранение дополнительного JS-кода.

Plugins. В этой папке храним все сторонние библиотеки, там же их инициализируем и настраиваем.

Hooks. Можно выделить отдельную папку для хранения кода, использующегося в Setup-компонентах (composition API).

Helpers. Помощники будут содержать вспомогательные функции, которые вы хотели бы использовать в своем приложении. Например, функции форматирования, конвертирования данных или валидаторы.

API/services. Папка содержит все функции вызова API.

Constants. Всё, на что в приложении будут ссылаться глобально, но не хранится в .env, можно хранить здесь. Это могут быть статические данные или, например, список типов глобальных окон, которые можно вызвать глобально (через эмиттеры).

Interfaces, enums. Если вы используете Typescript, то сразу можно выделить папку для типов и перечислений.

Эти папки описаны для примера и общего представления о том, как можно разделять кодовую базу. Конечно, можно видоизменять это под потребности проекта. Например, кто-то предпочитает хранить в /pages не только сам компонент страницы, но и папку этой страницы вместе со всеми используемыми блоками. Может быть удобно в папке /views хранить pages, store, blocks для каждого сервиса приложения.
 
Можно выбрать любой подходящий вариант, важно понимать, что несмотря на то, что компонент vue хранит в себе и template, и js, и css это не повод нагружать его слишком сильно.

2. Выделять все запросы к API

При создании приложения выполняются вызовы API для связи с сервером. Например, для получения данных, отправки формы и т. д. Vue не предоставляет официальной практики для выполнения вызовов API. Есть много способов их организации. В этом пункте я опишу подход, который чаще всего встречаю и использую.

Обычно для API создаем отдельную папку в корне проекта, где хранятся краткие запросы к сервисам, возвращающие промис.

Каждый файл хранит и экспортирует нужные функции по категориям. Например, products.js может содержать следующее:

export function getProduct (id) {
 return axios.get(`${API_URL}/products/${id}`)
}


export function postProduct (data) {
 return axios.post(`${API_URL}/products`, data)
}


export function patchProduct (data) {
 return axios.patch(`${API_URL}/products`, data)
}

Таким образом, мы документируем все доступные методы общения с сервером по категориям. И если путь изменится, мы поменяем его в одном файле, а не во всех местах, где используем запрос. Здесь важно, что мы отделяем чистый запрос от любой бизнес-логики. 

Далее мы можем использовать расширенный вариант функции в сторе или методах компонента, нанизывая бизнес-логику, хранение данных, отлов ошибок. Например, в сторе:

import { getProduct } from '@/api/products'
import { IProduct } from '@/interfaces'
const actions = {
 async fetchProduct ({ commit }, id: string): Promise<IProduct> {
   try {
     commit('setIsLoading', true)
     const response = await getProduct(id)
     commit('setProduct', response.data)
     return response.data
   } catch (err) {
     // отлавливаем ошибки
   } finally {
     commit('setIsLoading', false)
   }
 }
} 

Плохим вариантом будет просто выполнять запросы к урл в компоненте. Например, так:

methods: {
   getProductById (id) {
     return axios.get(`https://example.com/api/products/${id}`)
   }
}

Когда новому разработчику понадобится этот запрос в другом компоненте, ему придется копипастить или писать заново. А если поменяется урл запроса, то менять его в нескольких местах.

Основной путь к серверу лежит в .env, например VUE_APP_API_URL. Так что доступ к этой переменной возможно получить в любом месте приложения, и он может динамически изменяться в зависимости от окружения.

const API_URL = process.env.VUE_APP_API_URL

Основные плюсы этого подхода в том, что он достаточно прост и гибок, обеспечивает хорошую читабельность, сразу видно, какие запросы возможны в любой части приложения.

Это отлично работает для небольших и средних приложений, в которых не хочется усложнять структуру. Хотя для больших систем можно посмотреть в сторону разделения данных ORM. Подробнее об этом в документации для Vuex ORM. Это плагин Vuex, который позволяет разделять состояние приложения с точки зрения объектов данных (продуктов, пользователей и т. д.) и операций CRUD (создания, обновления, удаления).

3. Использовать стор, когда это действительно необходимо, и разделять на модули

Стор — централизованное хранилище для всех данных приложения с гарантированной предсказуемостью смены состояний.

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

Наличие общего доступа к таким элементам предотвращает огромную цепочку передачи данных из родительского компонента дочерним, а также предотвращает дублирование хранения данных в компонентах.

Но всегда ли стор необходим?

Часто хранилище используют неправильно. Периодически сталкиваюсь с тем, что в сторе хранят вообще все данные, даже если на это нет видимых причин. В этом случае стор может разрастись, и его будет труднее поддерживать. Данные могут тереться или не обновляться вовремя.

Например, при переходе с одного продукта на другой данные не обновляются сразу, и пользователь видит какое-то время «старые» данные. Нужно дополнительно заботиться не только о получении и хранении данных, но и об их обновлении/удалении.

А если вы хотите что-то изменить в структуре хранилища, вам придется сделать это в нескольких местах и обеспечить отсутствие багов во всех компонентах, которые к нему обращаются. В результате использование магазина может принести вам больше головной боли.

Таким образом, важно иметь уверенность в том, что хранение данных в едином источнике действительно необходимо. Прежде чем создавать еще один модуль в сторе, важно задать себе ряд вопросов:

  • Будут ли данные повторно использоваться где-то еще?

  • Могу ли я вместо этого использовать здесь локальное состояние?

  • Способствует ли использование стора улучшению архитектуры приложения?

Если ответы на эти вопросы вызывают сомнения, то, возможно, стоит использовать props или provide/inject.

Предположим, вы решили, что использование стора необходимо. Теперь важно понимать, что по умолчанию хранилище Vuex состоит из одного большого объекта, который содержит всё состояние приложения, мутации, действия и геттеры. Это может привести к раздуванию нашего приложения по мере увеличения его размера и сложности. Поэтому важно разделять хранилище на модули. Модули Vuex — это, по сути, небольшие независимые хранилища, которые объединены в более крупное центральное хранилище.

Примеры разделения на модули:

  • index.js — основной файл стора, который импортирует и хранит все модули;

  • auth.js — хранит состояние авторизации, логин, логаут, рефреш токена и т.д.;

  • user.js — хранит данные юзера и методы, связанные с ними;

  • config.js — хранит настройки приложения.

Создание модуля ничем не отличается от обычного центрального хранилища Vuex. Синтаксис почти такой же, за исключением того, что мы не создаем новый экземпляр хранилища в модуле. Вместо этого мы экспортируем объект, содержащий все его свойства, в экземпляр центрального хранилища:

const defaultState = () => {
 return {
   exampleData: {
     prop1: '1',
     prop2: '2'
   }
 }
}


const getters = {
  exampleGetter: (state) => state.exampleData.prop1
}


const mutations = {
 setExampleData (state, data) {
   state.exampleData = data
 }
}


const actions = {
 async fetchData ({ commit }) {
   try {
     const response = await getData()
     commit('setExampleData', response.data)
   } catch (err) {
     console.error(err)
   }
 },


}


export default {
 namespaced: true,
 state: defaultState,
 getters,
 actions,
 mutations
}

В то же время в основном хранилище мы импортируем модули:

import example from '@/store/example'
…
…

export default {
 state: defaultState,
 getters,
 actions,
 mutations,
 modules: { example }
}

Хорошая практика использовать атрибут namespaced:true у модуля, чтобы обеспечить его автономность. В этом случае вы сможете повторно использовать одно и то же имя для своего состояния, изменения и действий, не вызывая ошибок. В компонентах мы можем обращаться напрямую к модулю по его названию:

computed: {
   ...mapGetters('example', ['exampleGetter'])
}

Продолжение завтра

Написав первые 3 пункта, я поняла, что статья получается большой. Поэтому пришлось разбить ее на две части. Завтра в блоге AGIMA выйдет продолжение. Ссылка на него появится тут. А если у вас возникли вопросы по первым трем пунктам — задавайте в комментариях. Постараюсь оперативно ответить.

P. S. Вопросы также можно задать в нашем телеграм-канале для разработчиков. В нем уже более 500 человек — присоединяйтесь.

Комментарии (69)


  1. little-brother
    00.00.0000 00:00
    +1

    Почему бы не использовать axios.defaults.baseURL = 'https://api.example.com' ?


    1. gmtd
      00.00.0000 00:00

      Чтобы "https://api.example.com" лежал в env файле, а не в коде

      Удобней при сборке в различных окружениях (CI/CD)


      1. little-brother
        00.00.0000 00:00

        Так можете задать эту переменную в каком удобно файле и импортировать ее туда, где загружаете axios и прицепить axios.defaults.baseURL = API_URL.

        В итоге не потребуется писать каждый раз в определении функций "axios.post(`${API_URL}/строка_запроса`), а просто ограничиться axios.post(строка_запроса).

        PS Возможно я не прав, так как только учусь :)


        1. gmtd
          00.00.0000 00:00

          А, ну это да


  1. Kasheftin
    00.00.0000 00:00

    Некоторые принципы просто описать, но сложно реализовать. Например, использовать TypeScript - очень простой в описании принцип, очень помогает поддерживать и расширять приложение, но при этом очень затратный, если у вас большой кусок с легаси на js.

    Насчет API - все-таки сейчас OpenApi/Swagger для rest is a must, так же как codegen для graphql.

    Вроде как vue уходит от единого vuex store к pinia с multiple stores, поддержка не сложнее, универсальности больше.


  1. artemev
    00.00.0000 00:00

    Люди, вопрос глупый, но все же. Есть идея пет-проекта и встал вопрос что выбрать: React или Vue? Сделал пару мелких приложений на одном и на втором. Пока что опыта мало и не могу понять какой-же из фреймворков выбрать. У них есть какие-либо ключевые различия? Или может по удобству один предпочтительнее другого? В общем, у кого есть опыт просветите пожалуйста.


    1. stgunholy
      00.00.0000 00:00
      +6

      От скиллов зависит... Но я всегда выбираю vue. Мне кажется что на нем гораздо быстрее получается что-то готовое


      1. artemev
        00.00.0000 00:00

        Спасибо. А за счет чего быстрее?


        1. stgunholy
          00.00.0000 00:00
          +5

          Меньше религии... сопряжения всяких версий библиотек... но это ИМХО


        1. Batyodie
          00.00.0000 00:00

          Во vue очень много магии и сахара. На vue можно сделать одну задачу кучу разными способами и это в большинстве случаев плохо.

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

          У vue кривая входа на много легче, "easy to learn hard to master". Но чем сложнее начинается ваше приложение, тем больше появляется трудностей.

          Если вас не смущает все выше перечисленное, берите vue.


          1. Fragster
            00.00.0000 00:00

            Как будто бы с react ситуация лучше.


      1. Geobot
        00.00.0000 00:00

        Vue - легче и быстрее писать. Single file components существенно облегчают работу.


    1. gmtd
      00.00.0000 00:00
      +6

      Откройте в браузере facebook.com, поработайте в этом тормозном монстре, и потом подумайте - могут ли создавшие его люди придумать эффективный фреймворк


    1. SWATOPLUS
      00.00.0000 00:00
      +3

      Ну такое себе под постом про vue спрашивать об этом. Но мое мнение, что Angular или Vue. При этом Vue кажется более перспективным.

      В Angular уже есть дефолтаная архитектура, которая уменьшает боль при разработке.

      Во Vue дефолтаная архитектура менее жёсткая, собственно за хорошей архитектурой я и пришел а эту статью, но её так и не нашёл здесь. Во Vue 3 уже исправлено множество детских болезней Angular(а здесь они закостылены), плюс гораздо реже сталкиваешься с ограничениями фреймворка.

      В React разброд и шатание, это же просто библиотека. Я так и ни разу не увидел хорошей архитектуры на реакте. Киллер-фича реакта это jsx/tsx, так как можно гибко переиспользовать UI компоненты. Хуки, это красивый костыль, но все равно костыль. Composition Api во Vue получше будут. Да и jsx во Vue можно прикрутить.

      Vue сейчас очень развит, но нужно придумать хорошую архитектуру к нему. React я врагу не пожелаю, это черная дыра пожирающая бюджет любого проекта, много боли и костылей.


    1. Ilusha
      00.00.0000 00:00
      +1

      После vue3 на реакт обратно не хочется. Но у vue сильно меньшая экосистема, чем у реакта. Разнообразия меньше.

      Для пет-проекта вообще без разницы.

      В целом, попробовать: возьмите фреймворк quasar на vue, там из коробки есть всё, что нужно: и uikit, и всё настроено.


    1. Fragster
      00.00.0000 00:00

      Хабр на vue.


    1. venanen
      00.00.0000 00:00
      +1

      Как уже писали, я тоже выбираю Vue. Количество стандартного кода сильно меньше, и нет JSX.


  1. Vadiok
    00.00.0000 00:00

    Объясните, пожалуйста, зачем хранить ответ от апи в сторе. И как быть в таком случае, если апи отвечает с пагинацией и параметрами запроса, а этот эндпоинт необходимо использовать в разных компонентах с разными параметрами?


    1. vanxant
      00.00.0000 00:00
      +2

      Как зачем, чтобы страничка дёргалась и батарейка жралась на лишних рендерах


    1. Ilusha
      00.00.0000 00:00

      Их же все равно нужно хранить.

      В компоненте или сторе - не так принципиально. Но стор позволяет абстрагировать логику работы с данными: пагинация, фильтрация, сортировки. Можно и в хук вынести, не принципиально.

      Плюс стор позволяет кешировать: в спа можем снова маунтить компонент без запроса на данные и писать «было загружено 2 минуты назад».

      Кароч, причин может быть множество. Где-то нужно, а где-то нет.


      1. Vadiok
        00.00.0000 00:00
        +2

        То, что вы описали гораздо эффективнее делается вообще отдельным скриптиком кеша, которому не надо никакой реактивности. Смысл же стора в том, чтобы иметь общую для независимых компонентов реактивность. Уже много лет вижу такой странный паттерн, но адекватного ответа не вижу.


        1. Ilusha
          00.00.0000 00:00

          Сторы во вью берут на себя роль middleware для выделения бизнес-логики. В реакте это танк/саги. Операции над данными лучше уносить в сторы (или в хуки), а не хранить в компоненте.

          Pinia-сторы - это изолированные истории, которые могут использоваться ток для одного компонента. Типа мобх. Вообще, pinia-сторы очень близки к хукам.

          «Отдельный скриптик» будет хранить мне все параметры сортировок, фильтраций и пагинации, которые нужно восстановить во вьюхе? Да еще и process-стейтом будет управлять? У меня там вообще ошибка может быть. Или не быть респонса. Но хочу хранить состояние компонента, даже когда размаунчен.


          1. Vadiok
            00.00.0000 00:00

            Простейший пример:

            • Есть компонент Список товаров, в нем есть пагинация и фильтры, а еще в нем в попапе можно вызвать редактирование Товара.

            • В этом попапе Товара я могу привязать связанные товары, которые выбираются из того же компонента Списка товаров, но уже внутри этого попапа, там также есть фильтр.

            Вот и вопрос, как я тут со стором это реализую, если это один компонент, но в 2 местах и у каждого свои списки? И самое главное, зачем это надо делать через стор?

            Сторы во вью берут на себя роль middleware для выделения бизнес-логики

            Где вы такое определение взяли? Сторы во Vue - это реактивное хранилище данных. Накрутить в него можно все что угодно, но это нарушит Single Responsibility Principle, и, я считаю, что нарушит совершенно бесмысленно.

            «Отдельный скриптик» будет хранить мне все параметры...

            Ну стор же из коробки не будет вам ничего делать, вы же все равно скриптиками это все будете делать, только еще и в стор их запехнете.

            Pinia-сторы - это изолированные истории, которые могут использоваться ток для одного компонента

            Какой смысл тогда использовать для компонента свой единственный стор, если эти же данные могут храниться внутри компонента?


            1. Ilusha
              00.00.0000 00:00

              SRP - это абстракция. Дробить/обобщать - это уже на разработчике: как он видит смысл «единства».

              Давайте в Ваш пример добавим:

              • списков «товаров» может быть много.

              • В каждом у пользователя свои настройки сортировок/фильтраций.

              • Переключение между списками не сбрасывает параметры

              • Закрытие всех компонентов не сбрасывает установленные пользователем во время сессии параметры

              • Сами товары в разных списках - это разные типы


              1. Vadiok
                00.00.0000 00:00

                Ну, добавили всё это в мой пример - значит, надо хранить в сторе настройки фильтров. Зачем там хранить сами списки товаров, я до сих пор не понимаю. Это не бесплатная операция вообще-то.


                1. Ilusha
                  00.00.0000 00:00

                  Это можнт сказать бесплатная операция. Не нужно экономить на спичках.

                  Список храним, чтобы не запрашивать снова. Это не очевидно?


                  1. Vadiok
                    00.00.0000 00:00

                    Список храним, чтобы не запрашивать снова

                    Отлично, пользователь со списка переходит на редактирование сущности, сохраняет ее, возвращается, и что же он видит?


                    1. Ilusha
                      00.00.0000 00:00

                      И что же он видит?)


                      1. Vadiok
                        00.00.0000 00:00

                        Старые данные


                      1. Ilusha
                        00.00.0000 00:00

                        Нет. Фронт данные обновил в модели.


                      1. Vadiok
                        00.00.0000 00:00

                        Т.е. стор у вас ещё и отвечает за получение одной сущности, ее сохранение и обработку кеша? Как-то слишком много у него обязанностей.

                        А стор с другим типом сущностей эту же логику будет дублировать?


                      1. Ilusha
                        00.00.0000 00:00

                        import fetchList from '@/api/fetchList'
                        
                        // типизация будет чуть сложнее, естественно
                        export function getStore(name: string) {
                          return defineStore(name, {
                            // ...
                            actions: {
                              async fetch() {
                                if (['process', 'fetched'].includes(this.fetchStatus)) {
                                  return;
                                }
                                this.fetchStatus = 'process'
                                this.list = await fetchList()
                                this.fetchStatus = 'fetched'
                              }
                            }
                            // ...
                          })
                        }

                        Ужас какой, 5 строчках кода и получение, и сохранение, и обработка кеша на уровне стора. Как много обязанностей.

                        А вещь еще будут фабричные методы для сторов фильтрации и пагинации, чтобы все в кучу не складывать.
                        И фильтры могут лежать где угодно, а не в этом же компоненте. И пагинация.

                        По факту, я использую сторы как хуки, апи которых положили в глобальный скоуп. Ну или как реактивные инстансы.
                        И, что немаловажно, я могу эту логику переюзать в любом другом месте.


                      1. Vadiok
                        00.00.0000 00:00

                        Ужас какой, 5 строчках кода и получение, и сохранение, и обработка кеша на уровне стора.

                        На уровне стора у вас нет обработки кеша, нет никакого сохранения в этих 5 строчках. Обработка кеша у вас если и есть, то где-то внутри @/api/fetchList.

                        А раз обработка кеша у вас все-таки вне стора, тогда возвращаемся на несколько комментов выше

                        Список храним, чтобы не запрашивать снова

                        В таком случае он и в компоненте успешно будет храниться, только он из компонента при дестроее вычистится, а при сторе - нет, хотя и не будет использоваться, т.к. при любом запросе он затирается данными из ``fetchList``.

                        А вещь еще будут фабричные методы для сторов фильтрации и пагинации, чтобы все в кучу не складывать.
                        И фильтры могут лежать где угодно, а не в этом же компоненте. И пагинация.

                        Если эти данные требуются в нескольких компонентах, либо если эти данные надо хранить после дестроя компонента, чтобы получить при повторном вызове, то ОК. Но только список сущностей к этому никак не относится, т.к. в вашем же примере он повторно запрашивается со стороны.


                      1. Ilusha
                        00.00.0000 00:00

                        Как же нет кеширования, если есть? Первое уже условие в экшене. Это самое элементарное кеширование последнего респонса. Кеширование по определению. Не похоже на парметризированное кеширование на уровне api? Ну так оно не нужно в данном контексте. Прикрутим пагинацию, фильтрацию и сортировки - все становится несколько сложнее. Но суть останется той же.

                        Зачем: чтобы перед маунтом компонента всегда делать fetch и не париться.

                        И как нет сохранения? А куда сохраняется результат запроса?

                        > только он из компонента при дестроее вычистится
                        Бинго. Из моего первого коммента в треде:
                        > можем снова маунтить компонент без запроса на данные

                        Делаем запрос данных в компоненте: имеем самые свежие при открытии. Храним в сторе: получаем уже загруженные и доп нагрузку на бизнес-логику фронта: обновление моделей или сброс флага на "нужно снова сфетчить в след раз".

                        Можно другой пример привести: чат с загруженной историей сообщений (юзер скроллил и ему подрузились страницы).
                        Юзер ушел из чата, потом вернулся: мы должны запросить новые, но старые не трогаем. А еще хорошо бы позицию скролла сохранять. Сложно? Сложно. Стор лишь инстурумент.


                      1. Vadiok
                        00.00.0000 00:00

                        Простите, но у вас вообще какая-то билеберда, а не стор. Вы такой код с кем-нибудь в команде используете?


                      1. Ilusha
                        00.00.0000 00:00

                        Я вам про концепции, а вы про код, которого даже не видели :)

                        Думаю, вы просто не поняли идею, потому что то, что я говорю - простейший паттерн, который еще в backbone использовали.


              1. Vadiok
                00.00.0000 00:00

                И в догонку, комментарием выше вы писали:

                Pinia-сторы - это изолированные истории, которые могут использоваться ток для одного компонента

                Как вы думаете, что произойдет в плане потребления оперативки в двух вариантах:

                1. Компонент вызвался, список каких-то сущностьей пришёл с бэка, сохранился в компоненте, компонент убрался (например при смене роута).

                2. Все аналогично, но список сохранился в сторе.


                1. Ilusha
                  00.00.0000 00:00

                  Список не весит ничего, на уровне погрешности.

                  Сетевое взаимодействие - это уже затраты времени. С мобильным третьим - существенные. В Африке - жопа.

                  А вот мощности телефонов даже в Африке хватает, чтобы хранить несколько сот килобайт в памяти.


        1. dev_uska
          00.00.0000 00:00

          На самом деле, адекватного ответа не будет. Мы пришли к решению в виде tanstack query для vue. Там и в документации достаточно популярно объясняется разница между состоянием приложения и асинхронным состоянием сервера. Решение хорошее, очень много проблем решает.


  1. gmtd
    00.00.0000 00:00
    +7

    Статья как будто устарела на год минимум

    Vuex?

    Где composables?

    axios, кстати, лучше тоже обернуть (в http), и вызывать через обертку


    1. vanxant
      00.00.0000 00:00
      +1

      axios лучше закопать и один раз разобраться с нативным fetch


      1. Ilusha
        00.00.0000 00:00

        Его в nodejs завезли буквально недавно. Не смотрел, он из экспериментальной фичи вышел?

        Плюс есть некоторое различия в поведении, возможно и бэк нужно будет скорректировать.


        1. vanxant
          00.00.0000 00:00

          Вы шутите, он в хроме и огнелисе с 2015 года в продакшн!

          С бэком да, сталкивался с рукожопами, которые определяют ajax запрос по наличию http заголовка x-requested-with: xmlhttprequest (вместо нормального accept). Но это было ещё во времена миграции с Yii 1.1 и $.ajax .


          1. Fragster
            00.00.0000 00:00

            Вам про ноду, а вы про браузеры. Есть такая вещь - ssr. И гораздо проще использовать одну библиотеку и на сервере и на клиенте.


            1. vanxant
              00.00.0000 00:00

              Спасибо, за ssr я немного в курсе. На ноде он с 2021, а полифил (node-fetch) доступен 8 лет как.

              Ну и да, хотелось бы посмотреть соотношение количество проектов на vue которые используют ssr и вообще бэкенд на nodejs по отношению ко всем проектам на vue. Там хотя бы 1% есть?


              1. Fragster
                00.00.0000 00:00

                Ну, например, весь nuxt. С quasar тоже включается парой строк в конфиге. Да и в принципе не так, чтобы сложно сделать (но со всякими сторами, конечно, есть нюансы).


                1. vanxant
                  00.00.0000 00:00

                  Ну даже по статистике npmjs.com соотношение загрузок nuxt/vue 1:8. Но это конечно ни о чём, дофиглиард сайтов подключают vue.js тупо c CDN или тащат к себе как тот же битрикс. Потому что одно дело подсунуть json с какого угодно бэка и тэг <template> и оно тупо работает, совсем другое подписаться на вечные пляски с черной дырой node_modules.


                  1. Fragster
                    00.00.0000 00:00

                    Там хотя бы 1% есть?

                    vs

                    по статистике npmjs.com соотношение загрузок nuxt/vue 1:8

                    Предлагаю на этом остановиться.


                    1. gmtd
                      00.00.0000 00:00

                      Разве все проекты на Накст используют SSR?
                      Это лишь одна из его особенностей


          1. Ilusha
            00.00.0000 00:00

            А в safari с 2017. А относительно массовый отказ от ie пошел вот ток недавно.

            Помню что когда хотел взять фечт, то оказалось, что в нем еще не было отслеживания прогресса загрузки. Но может этот давно было.


    1. little-brother
      00.00.0000 00:00

      Ссылка на pinia есть, насколько понял vuex остается пока для больших приложений: https://habr.com/ru/post/666250/ Конечно у pinia логика более прозрачная, чем у vuex с кучей экшн и их мутациями.

      axios, кстати, лучше тоже обернуть (в http), и вызывать через обертку

      Можно в двух словах зачем так делать? У меня тут опыта не хватает понять функционал заворачивания :*-)


      1. gmtd
        00.00.0000 00:00
        +1

        Vuex уже год как не рекомендуется для использования в официальной документации Vue 3. Размер приложения тут ни при чем.

        Axios обернуть лучше чтобы при необходимости/желании можно было легко заменить его на что-то другое - тот же fetch или еще что-то - сейчас хватает более современных и удобных библиотек для сетевых запросов

        Точно так же заворачивание компонента в свою обертку позволяет потом легко заменить его. Например завертка v-btn в свой BaseButton позволяет сделать замену в одном месте, а не по всему коду.


        1. little-brother
          00.00.0000 00:00

          Спасибо!


        1. Alexufo
          00.00.0000 00:00

          На что заменить axios?


          1. Tinkz
            00.00.0000 00:00

            хочу попробовать got


            1. Alexufo
              00.00.0000 00:00

              это под ноду, axios это же фронт


              1. Fragster
                00.00.0000 00:00

                axios это и фронт и бэк, что незаменимо при SSR


    1. nkalacheva Автор
      00.00.0000 00:00

      Можно выбрать любое хранилище, в статье про стор в принципе. Моя ошибка, что не упомянула про pinia. Vuex или Pinia в данном случае не играет роли, для pinia +- те же принципы работают

      сomposables здесь лежат в папке hooks (тут так назвала, но сomposables даже более очевидно +)

      Про использование обертки - отличное замечание, это как раз выйдет во второй части статьи


    1. Ilusha
      00.00.0000 00:00

      Тогда и типизацию нужно обернуть.


  1. SWATOPLUS
    00.00.0000 00:00
    +1

    Views/pages

    Так вьюшки или страницы? Или это два разных типа компонент? Как это вы называете? Давайте придем к единому стандарту.

    API/services

    Аналогичный вопрос.

    Interfaces, enums

    Вы действительно считаете, что так удобно? Быть может стоит хранить интерфейсы енумы по месту первичного использования? Например если это используется в пропсах блока, то лучше к блоку. Если возвращается api, то рядом с api.

    export function getProduct (id) { return axios.get(`${API_URL}/products/${id}`) } export function postProduct (data) { return axios.post(`${API_URL}/products`, data) } export function patchProduct (data) { return axios.patch(`${API_URL}/products`, data) }

    Почему это три функции, а не класс с двумя методами (я видел, что именно так в Angularделают)? А почему нет ts-типов?

    import { getProduct } from '@/api/products' import { IProduct } from '@/interfaces' const actions = { async fetchProduct ({ commit }, id: string): Promise<IProduct> { try { commit('setIsLoading', true) const response = await getProduct(id) commit('setProduct', response.data) return response.data } catch (err) { // отлавливаем ошибки } finally { commit('setIsLoading', false) } } }

    А почему типы здесь появились?

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

    Зачем здесь стор? Почему мы не можем сделать глобальный объект-экземпляр класса, который хранит эти данные? Зачем писать куча каши со стором? Мне кажется что использовать сервисы как в Angular и получать их через inject, было бы удобнее (Там правда ручками надо поколдовать с созданием зависимостей), но у меня нет опыта реализации этого на множестве промышленных проектах.

    Вообще весь 3-й совет, я так и не понял. Если данные могут использоваться на нескольких страницах то кажется должен подходить provide/inject. В класс-обертка для api-сервиса, может помочь реализовать кэширование? В Angular не используют глобальный стор (ну кроме пары извращенцев) и как-то живут. Почему этот подход нельзя использовать во Vue?

    САМОЕ ГЛАВНОЕ!!! Вы забыли совет номер 0: используйте Typescript. Настройте линтер на strict mode, запрет any, а так же js-файлов.

    @nkalacheva


    1. nkalacheva Автор
      00.00.0000 00:00
      +1

      1) Vue дает много свободы и возможность формировать свою архитектуру. На вкус разработчика, тут views = pages, главное чтоб логика разделения сохранялась и там хранились именно страницы. 

      2) В небольшом/среднем проекте сразу папка api.

      В проектах побольше services, внутри уже папка api + дополнительные папки (helpers, generators и т.д), где можем хранить функции сериализации, слипы и др

      2) Для простоты расписывала плоскую структуру, папки можно менять или вкладывать. Да, можно выделять интерфейсы по месту хранения, если это удобно. Иногда их вообще не выделяют, как итог - несколько одинаковых интерфейсов разбросаны по проекту в разных папках на больших проектах.

      Мне удобнее видеть все в одном месте. Файл с типами тоже можно сделать композитным.

      3) Здесь просто пример выделения, можно использовать, как классы, так и функции. В зависимости от проекта. Можно сделать объект product и внутри него методы get, post, patch.

      4) Про стор, как раз пишу, что он не везде необходим и можно обойтись без него. (https://github.com/vuejs/vuex/issues/236#issuecomment-231754241)

       На сколько знаю, в ангуляре просто вместо стора используются сервисы. Могу ошибаться, +- та же штука, где данные доступны глобально. Во вью просто нет DI движка из под коробки и зависимости явно импортируются.

       На тех же объектах можно построить реактивность с помощью Composition API и отказаться от стор в принципе. Но, для меня большой плюс стор - это выделенное под хранилище представление во вьюшных девтулзах, можно сразу видеть все объекты, а не только через компоненты смотреть. На больших проектах это помогает.

       provide/inject подойдет, если у компонентов общий родитель, но мы не сможем делиться данными с соседями, + жирный минус, что при использовании TS теряем типизацию

      5) Согласна, что typescript очень удобен и мне лично упрощает жизнь его использование. Линтеры и прекоммиты маст хев


    1. Ilusha
      00.00.0000 00:00

      Зачем использовать provide/inject, когда есть pinia? Ноль каши. И очень удобно.


      1. SWATOPLUS
        00.00.0000 00:00

        Читал, что там есть некое подобие di, но так и не понял как это заставить работать. Может быть у вас есть пример кода/проект где настроем di?


        1. Ilusha
          00.00.0000 00:00

          Смотрел на pinia-di, но пока не понял, зачем мне это нужно. Тоже хочется увидеть сложные кейсы, особенно в контексте ts.

          Тут из без di мучаюсь с выводом типов через pinia в сложных кейсах (типа динамической генерации однотипных сторов).


      1. gmtd
        00.00.0000 00:00

        provide/inject - элемент языка(фреймворка)

        pinia - сторонняя библиотека

        Может человек хочет создать свой переиспользуемый модуль без зависимостей. Тогда provide/inject вполне спасает.


        1. Ilusha
          00.00.0000 00:00

          Это была ирония. Что одно, что втрое - лиши инструменты


  1. bobnobrain
    00.00.0000 00:00

    А для чего отдельная папка под интерфейсы и енамы? Кажется, они должны лежать рядом с тем местом, где они подходят по смыслу (рядом с функциями, например, которые их принимают/возвращают), а не в отдельной папке чисто для них.


  1. tatar88t
    00.00.0000 00:00

    Пока из первых 3-х пунктов ничего нового, прописные истины описаны. Пишите хороший код, а плохой не пишите!


  1. BelikovEI
    00.00.0000 00:00
    +1

    Всем привет!

    По первому принципу, рекомендовал бы ознакомится с https://feature-sliced.design/ (FSD). Отличное решение для масштабирования больших проектов. По началу кажется сложным, но через месяц обсуждений и привыкания - все встает на свои места. По итогу у вас будет уже готовая документация того, как хранить файлы и как они могут между собой взаимодействовать.

    По третьему принципу достаточно спорно. В приведенном примере "карточки продукта". Обычно, подобные карточки включают в себя несколько вложенных компонент. И тогда локальное хранение состояния приведет к "props drilling", что не есть хорошо. Да и в целом, как по мне, pinia гораздо удобнее vuex. Кстати pinia идеально работает с FSD.