惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

J
Java Code Geeks
腾讯CDC
博客园 - 聂微东
爱范儿
爱范儿
罗磊的独立博客
P
Proofpoint News Feed
博客园 - Franky
博客园 - 三生石上(FineUI控件)
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
酷 壳 – CoolShell
酷 壳 – CoolShell
Jina AI
Jina AI
Blog — PlanetScale
Blog — PlanetScale
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 司徒正美
美团技术团队
MongoDB | Blog
MongoDB | Blog
WordPress大学
WordPress大学
A
About on SuperTechFans
I
InfoQ
博客园_首页
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
H
Help Net Security
Microsoft Azure Blog
Microsoft Azure Blog
G
Google Developers Blog

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания
Как не надо писать Store в Pinia (Vue). Разбираем на выду...
Pnym · 2026-04-26 · via Все публикации подряд на Хабре

Сегодня посмотрим на вымышленный пример, как не надо делать стор. Любые совпадения - случайность. Все истории выдуманы.

Представьте: есть у нас герой Алекс. Перекидывают его на проект - «поправить пару простых багов, делов на пять минут». Открывает Алекс код, а там… У него сердце замирает. Подумаешь, с кем не бывает. Но внутри начинается дилема: просто пофиксить баги и забыть этот ужас как страшный сон, либо как настоящий богатырь проектов взять и отрефакторить весь этот бардак. Сделать по-человечески, заложить нормальную основу. Да, потом спросят за новые баги - ну и что. Зато внутри тепло разольётся, что не забил на плохой код и навёл порядок.

Ладно, хватит слов. Давайте к практике. Вот такой стор увидел Алекс (специально упростил, но суть та же):

export const usePropertyStore = defineStore('property', () => {
  const items = ref<Record<string, any>>({})
  const isFetching = ref(false)

  watch(
    items,
    (val) => {
      localStorage.setItem('items', JSON.stringify(val))
    },
    { deep: true }
  )

  const activeType = computed(() => localStorage.getItem('type'))
  const currentItem = computed(() => {
    if (!activeType.value) return null
    return items.value[activeType.value]
  })

  async function load() {
    isFetching.value = true
    const result = await fetchProperties()
    isFetching.value = false
    return result
  }

  return { items, isFetching, activeType, currentItem, load }
})

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

Первое, что испугало - работа с localStorage прямо в сторе.

Сама по себе идея кешировать данные не криминал, но здесь ни одного хелпера, ни обработки ошибок. Если вдруг в localStorage что-то битое, JSON.parse выплюнет исключение и приложение упадёт в самый неподходящий момент. Я не говорю вообще не использовать localStorage, но в большинстве проектов он не нужен. А если и нужен - возьмите готовое решение от Pinia (плагин persistedstate), не изобретайте велосипедов. Если сами пишете обёртку - будьте добры, обрабатывайте парсинг и запись аккуратно, тестируйте. Здесь же просто запись без всякой защиты.

Что бы Алекс хотел увидеть:

export const saveToCache = (key, data) => {
  try {
    localStorage.setItem(key, JSON.stringify(data))
  } catch (e) {
    // ...
  }
}

Watch в сторе - почти всегда плохая практика.

Подняв историю коммитов и обсуждения в задачах, Алекс понял: это была незавершённая задумка - сохранять items в localStorage, чтобы после перезагрузки страницы сразу показывать данные, а не делать запрос заново. Логика понятна, но зачем городить watch с deep, который будет дёргаться на каждое изменение вложенных полей? Если бэк отдаёт быстро - лучше просто делать запрос по необходимости. Если уж очень надо кешировать - опять же, есть нормальные плагины. В нашем случае Алекс выяснил, что items можно было спокойно не хранить вообще, бэк давал данные за миллисекунды. Выпилили watch вместе с localStorage. Даже если необходимо сохранять, то есть понимание, где вызывается метод загрузки items, и мы можем сами отслеживать этот момент и вызывать localStorage.

Дальше полез в computed, который читает localStorage.getItem.
Честно, я сам офигел, когда Алекс показал. Вы когда-нибудь вешали чтение из localStorage прямо в computed? Мозг ломается, потому что это не работает.

activeType и currentItem выглядят реактивными, но это иллюзия. localStorage.getItem() - синхронная функция, которая не умеет сообщать Vue об изменениях. При первом рендере значение закешируется, а дальше, даже если пользователь обновит хранилище через консоль или другой компонент, computed не пересчитается.

Реактивность работает только с ref и reactive.

А как это использовалось? Нашлось быстро: в каком-то обработчике клика:

localStorage.setItem('selected_type', type)

И сразу же роутер пушили на новую страницу. Там уже стор заново обращался к этому computed и подхватывал значение. Жесть. Никогда так не делайте. Если нужно передать параметр между страницами - используйте роутер по-человечески: пропсы через route params, query. Это чище и предсказуемо.

Переменная isFetching в сторе резанула глаз.
Спрашиваю Алекса: «Зачем она здесь?» Он плечами пожал. Оказалось, isFetching во всём приложении больше никто не читал. Только сам метод load его менял - и всё. Ребята, запомните: не надо писать переменные в сторе про запас. Если вам нужно отслеживать состояние загрузки - создайте локальный ref в компоненте, где вызываете метод. Это и чище, и понятнее. Уносим loading из стора и больше не паримся.

Как это выглядит после правки

export const usePropertyStore = defineStore('property', () => {
  const items = ref<Record<string, any>>({})
  const activeType = ref<string | null>(null)

  const currentItem = computed(() => 
    activeType.value ? items.value[activeType.value] : null
  )

  async function loadProperties() {
    return await fetchProperties()
  }

  return { photos, items, activeType, currentItem, loadProperties }
})

Рефакторинг - это не про «сделать красиво». Это про «сделать предсказуемо». Когда стор не делает лишних движений, не кеширует то, что не должен, и не пытается управлять роутером, его легче тестировать, расширять и передавать по наследству. А если вам вдруг достался вот такой «кошмар» - не паникуйте. Разберите по шагам, уберите лишнее, замените на стандартные решения. И да, всегда гоняйте тесты перед мерджем. Удачного кодинга :)