Практическое сайтоводство

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

Устанавливаем Cursor и настраиваем проект

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

В идеале нам нужна подписка Pro для Cursor, которая стоит $20 за месяц использования и даёт достаточно токенов для описанных задач. Купить подписку, если вы находитесь в РФ, можно либо картой зарубежного банка, либо через сервисы-посредники. Есть бесплатная версия, в ней урезаны лимиты и нельзя выбирать, какая модель будет использована, но можно начать и с неё.

По умолчанию Cursor загружается только с английским интерфейсом, есть возможность добавить русскую локализацию через установку языкового пакета (нажимаем Ctrl+Shift+P и набираем Configure Display Language), но я им не пользуюсь, поэтому в своих примерах показывать не буду.

Для нашего проекта создаём отдельную папку где-нибудь в удобном месте в файловой системе — я у себя её назову interview-preparation — и потом открываем эту папку через интерфейс File > Open Folder в Cursor.

Пустой проект в Cursor с выбранной моделью Gemini 3 Pro

На скриншоте видно, что у меня выбрана модель Gemini 3 Pro. Любая другая модель из приведённого выше списка справится не хуже

Строим план подготовки

В первую очередь, наш план должен опираться на конкретные требования из вакансии. Для этого берём имеющуюся вакансию, копируем из неё требования, список задач, над которыми предстоит работать и т. д. Условия работы копировать не нужно. Сохраняем эту информацию в отдельный файл (меню File > New Text File) с понятным названием, например, Требования.md. По моему профилю требования выглядят примерно так:

### Senior Frontend Developer (Vue.js + TypeScript)

Ищем опытного разработчика (5+ лет) для проектирования архитектуры, создания сложных интерфейсов и технического лидерства в команде.

**Задачи:**
*   Разработка на **Vue 3 (Composition API) + TypeScript**.
*   Принятие архитектурных решений (выбор библиотек, проектирование модулей).
*   Оптимизация производительности (Core Web Vitals, SSR на **Nuxt 3**).
*   Настройка CI/CD пайплайнов и проведение Code Review.
*   Менторство младших разработчиков.

**Требования:**
*   **Стек:** Глубокие знания Vue 3, TypeScript (Strict), Pinia, Nuxt 3.
*   **Инструменты:** Vite, Webpack, Docker, Git.
*   **Вёрстка:** Адаптивная вёрстка, Tailwind CSS или SCSS.
*   **Тестирование:** Опыт написания Unit (Vitest) и E2E тестов.
*   **Soft Skills:** Самостоятельность, продуктовое мышление, опыт в Agile.

**Будет преимуществом:**
*   **AI-Fluency:** Использование Cursor/Copilot/ChatGPT для ускорения разработки.
*   **Архитектура:** Опыт работы с Feature-Sliced Design (FSD) или микрофронтендами.
*   **API:** Опыт работы с GraphQL (Apollo) и WebSocket.

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

Открываем окно чата с ИИ-агентом через Ctrl+I и формулируем запрос следующим образом, стараясь предоставить всю важную информацию, которая у нас есть:

Я готовлюсь к техническому собеседованию на роль Senior Front-end Engineer с фокусом на Vue.js + TypeScript. На предыдущем месте работы я работал в роли Senior Full Stack Engineer и активно использовал Vue.js и TypeScript, у меня больше 5 лет опыта с этими технологиями.

Формат собеседования: видеозвонок через Zoom примерно на полтора часа. Я ожидаю, что какая-то часть будет посвящена глубоким техническим вопросам, а какая-то часть будет отведена на решение задач, но это будут задачи не на знание алгоритмов.

В прикреплённом файле перечислены конкретные требования из вакансии, на которую я претендую.

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

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

Набранный промт в Cursor и корректно прикреплённый файл с дополнительным контекстом для агента

Набранный промт и корректно прикреплённый файл с дополнительным контекстом для агента

Отправляем. Агент проанализирует запрос и сгенерирует нам файл с планом подготовки.

План подготовки, сгенерированный нейросетью
# План подготовки к собеседованию Senior Frontend (Vue.js + TS)

Стратегия подготовки: 10 часов, разбитых на 5 блоков. Упор на систематизацию, "подкапотную" работу и архитектуру.

---

## Блок 1: Глубокое погружение во Vue 3 и Архитектуру (2 часа)
*Цель: Показать понимание того, как работает реактивность, и умение строить масштабируемые приложения.*

### Теория (1 час)
*   **Reactivity System:** Как именно работает `Proxy` во Vue 3? Отличия `ref` vs `reactive` (deep vs shallow). Как работает Dependency Tracking?
*   **Composition API Patterns:** Паттерн "Composable" (hook) против Mixins (почему миксины плохи). Инверсия зависимостей через `provide/inject`.
*   **Architecture & FSD:** Изучить методологию **Feature-Sliced Design (FSD)** (упомянуто в вакансии как плюс). Слои: `app`, `pages`, `widgets`, `features`, `entities`, `shared`.
*   **State Management:** Pinia vs Vuex. Когда стейт-менеджер не нужен? (Ephemeral state vs Server state).

### Практика (1 час)
*   **Задача:** Спроектируйте (на бумаге или псевдокоде) архитектуру файловой структуры для интернет-магазина, используя принципы FSD.
*   **Кодинг:** Напишите сложный composable `useAsyncState`, который обрабатывает загрузку, ошибки и отмену запроса (AbortController), используя TypeScript Generics.

### Контрольные вопросы
*   *Чем `watchEffect` отличается от `watch`?*
*   *Как избежать потери реактивности при деструктуризации пропсов? (`toRefs`)*
*   *В чем плюсы и минусы FSD по сравнению с классической группировкой по типу файлов?*

---

## Блок 2: TypeScript (Strict) и Типизация Vue (2 часа)
*Цель: Доказать, что вы пишете типобезопасный код, который легко поддерживать.*

### Теория (45 мин)
*   **Utility Types:** `Pick`, `Omit`, `Partial`, `Record`, `ReturnType`.
*   **Advanced:** Generics, Discriminated Unions (очень важно для стейта), Type Guards (`is`), Type Narrowing.
*   **Vue + TS:** Типизация `props`, `emits`, `provide/inject`, типизация Pinia store.

### Практика (1 час 15 мин)
*   **Задача:** Создать компонент "Умная таблица" (Data Table). Она должна принимать массив объектов любого типа `<T>` и конфигурацию колонок, которая строго ссылается на ключи этого `T`.
*   **Рефакторинг:** Возьмите кусок JS-кода (например, обработка ответа API) и перепишите его на строгий TS с валидацией (можно использовать Zod, если знакомы, или просто Type Guards).

### Контрольные вопросы
*   *В чем разница между `interface` и `type`? Когда использовать `unknown` вместо `any`?*
*   *Как типизировать слот во Vue компоненте (Generic Components)?*

---

## Блок 3: Nuxt 3, SSR и Производительность (2 часа)
*Цель: Продемонстрировать знания серверного рендеринга и оптимизации (Core Web Vitals).*

### Теория (1 час)
*   **Rendering Modes:** SSR, CSR, SSG, ISR. В чем разница, когда что применять? Гидратация (Hydration) и проблемы с ней (Hydration Mismatch).
*   **Nuxt 3 Specifics:** `useAsyncData` vs `useFetch`. Server Routes (`/server/api`). Nitro Engine.
*   **Performance:**
    *   LCP (Largest Contentful Paint), CLS (Cumulative Layout Shift), INP (Interaction to Next Paint).
    *   Code Splitting (ленивая загрузка компонентов и роутов).
    *   Tree shaking.

### Практика (1 час)
*   **Аудит:** Откройте любой "тяжелый" сайт, запустите Lighthouse/Performance tab в DevTools. Найдите узкие места (Long Tasks, Layout Thrashing).
*   **Кодинг:** Напишите компонент, который загружает тяжелую библиотеку (например, для графиков или карт) только тогда, когда компонент попадает во viewport (`IntersectionObserver`).

### Контрольные вопросы
*   *Почему при использовании SSR нельзя обращаться к `window` или `document` в `setup`?*
*   *Как оптимизировать CLS при загрузке картинок или шрифтов?*

---

## Блок 4: Live Coding (Решение задач) (2.5 часа)
*Цель: Подготовиться к лайв-кодингу без алгоритмов (DOM, API, UI логика).*

### Типовые задачи для Senior Vue
1.  **Virtual Scroll:** Реализовать список из 100,000 элементов, который рендерит только видимую часть (без библиотек).
2.  **Tree View:** Рекурсивный компонент для отображения папок/файлов с возможностью сворачивания/разворачивания.
3.  **Debounced Search:** Поле ввода с автодополнением, кэшированием результатов и обработкой Race Conditions (когда второй запрос пришел раньше первого).
4.  **Modal Service:** Создать сервис для открытия модалок через JS (`openModal(component, props)`), используя `Teleport` и `h` (render function).

### Формат тренировки
*   Выберите 2 задачи.
*   Поставьте таймер на 30-40 минут.
*   Пишите код в чистом редакторе (или песочнице Vue SFC Playground), комментируя свои мысли вслух.

---

## Блок 5: System Design и Soft Skills (1.5 часа)
*Цель: Подготовиться к обсуждению опыта и проектированию систем.*

### Активности
1.  **System Design:** "Спроектируйте аналог Google Drive".
    *   *Фронтенд:* Как хранить состояние загрузки? Как реализовать Drag-n-Drop? Виртуализация списка файлов? Офлайн-режим?
2.  **Behavioral:** Подготовьте истории по методу **STAR** (Situation, Task, Action, Result):
    *   "Расскажите о самом сложном баге".
    *   "Как вы разрешили технический спор в команде?"
    *   "Пример неудачного архитектурного решения".
3.  **Code Review:** Представьте, что вы смотрите код джуниора. На что вы обратите внимание в первую очередь? (Безопасность, читаемость, перформанс).

Комментарии по используемому промту и полученному плану

  • Нейросеть плохо понимает, с какой скоростью человек может усваивать информацию и выполнять задачи. Нейросеть может указать, что на конкретную задачу нужно 30 минут, но если человек подобную проблему никогда не решал, у него запросто может уйти несколько часов и больше.
  • Тем не менее, нам необходима какая-то единица измерения, чтобы контролировать, насколько подробный план мы получаем, поэтому я использую эти условные часы.
  • Количество часов лучше занизить в пару раз: если у меня есть 40 часов на подготовку, лучше составить план на 20 или даже 10 часов. А если удастся освоить материал раньше времени, всегда можно сгенерировать дополнительный план.
  • Изначально я в промте указывал конкретное число дней и просил разбить план по дням, но я успевал выполнить в лучшем случае 60-70% плана на день, и с каждым следующим днём начинал отставать сильнее и сильнее, что по итогу ломало всю подготовку. Поэтому строить план в более абстрактных блоках мне кажется правильнее.
  • Поскольку мы заранее не можем знать, сколько времени займёт работа по плану, лучше не ждать, пока нам назначат конкретную дату технического собеседования, а начинать готовиться заранее, используя в качестве ориентира текст «идеальных» вакансий, то есть тех компаний, куда мы бы хотели попасть.

Готовим детальный план по каждому из разделов

Мы получили общий план на несколько дней подготовки. Будем использовать его как основу. Когда мы готовы подступиться к очередному разделу из плана, просим ИИ сделать детальный план для этого раздела. Используем следующий промт:

Я приступаю к Блоку 1: Глубокое погружение во Vue 3 и Архитектуру. Используя общий план подготовки, подготовь подробный план для повторения материала этого раздела, добавь ссылки на материалы, сделай чёткие формулировки для практических задач. Сохрани план этого раздела в отдельный файл.
Набранный промт в Cursor и прикреплённый файл с дополнительным контекстом для агента

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

Детальный план для первого блока тем
# Блок 1: Глубокое погружение во Vue 3 и Архитектуру
**Время на выполнение:** ~2 часа
**Цель:** Освежить знания о внутренней работе Vue, разобраться с FSD и доказать умение писать сложные абстракции.

---

## Часть 1: Теория (1 час)

### 1. Система реактивности (Reactivity System)
Нужно четко понимать, как Vue отслеживает изменения. На собеседовании часто спрашивают "почему переменная изменилась, а компонент не перерендерился?".

*   **Что повторить:**
    *   **Proxy vs Object.defineProperty:** Почему Vue 3 перешел на Proxy? (Поддержка добавления свойств, массивов, Map/Set).
    *   **Ref vs Reactive:**
        *   `ref` везде? Или `reactive` для группировки? (Best practice: `ref` — стандарт де-факто для DX).
        *   Разница между `shallowRef` и `triggerRef` (оптимизация производительности).
    *   **Dependency Tracking:** Как работают `track` (get) и `trigger` (set).

*   **Материалы:**
    *   [Vue Docs: Reactivity in Depth](https://vuejs.org/guide/extras/reactivity-in-depth.html)**Обязательно к прочтению**.
    *   [Vue Docs: Reactivity Fundamentals](https://vuejs.org/guide/essentials/reactivity-fundamentals.html) — Освежить нюансы `reactive()` limitations.

### 2. Composition API Паттерны
Переход от Options API к Composition API — это не просто смена синтаксиса, это способ переиспользования логики.

*   **Что повторить:**
    *   **Composables (Hooks):** Соглашения по именованию (`use...`), возврат refs.
    *   **Dependency Injection:** `provide` / `inject` с `InjectionKey` для строгой типизации. Как избежать "Prop Drilling".
    *   **Lifecycle Hooks:** Как они работают внутри `setup()`.

*   **Материалы:**
    *   [Vue Docs: Composables](https://vuejs.org/guide/reusability/composables.html)
    *   [Vuejs: Coding Better Composables](https://vueschool.io/articles/vuejs-tutorials/coding-better-composables-1-5-tips-to-write-better-composables/) (статья)

### 3. Архитектура Feature-Sliced Design (FSD)
Это стандарт для крупных фронтенд-проектов в СНГ. Показывает, что вы умеете мыслить модулями, а не просто "компонентами".

*   **Основные слои (снизу вверх):**
    1.  **Shared:** UI-кит, утилиты, API клиент (нет бизнес-логики).
    2.  **Entities:** Бизнес-сущности (User, Product, Cart).
    3.  **Features:** Действия пользователя (AddToCart, AuthByPhone).
    4.  **Widgets:** Самостоятельные блоки (Header, ProductList).
    5.  **Pages:** Страницы (композиция виджетов).
    6.  **App:** Глобальные настройки, роутинг, провайдеры.

*   **Правило:** Модуль может импортировать только "снизу", но не "сверху" или "сбоку" (из того же слоя).

*   **Материалы:**
    *   [FSD: Official Documentation](https://feature-sliced.design/)
    *   [FSD: Методология за 15 минут (Видео или статья)](https://habr.com/ru/articles/593575/)

---

## Часть 2: Практика (1 час)

### Задача 1: Архитектурное проектирование (FSD)
**Контекст:** Интернет-магазин электроники.
**Задание:** Напишите структуру папок (в виде дерева текста) для страницы "Карточка товара".

**Функционал страницы:**
1.  Отображение фото и характеристик товара.
2.  Кнопка "Добавить в корзину" (меняет состояние корзины).
3.  Блок "Похожие товары" (слайдер).
4.  Отзывы пользователей.

**Требование:** Распределите компоненты и логику по слоям: `pages`, `widgets`, `features`, `entities`, `shared`.

---

### Задача 2: Coding Challenge — `useAsyncState`
Напишите composable на TypeScript для выполнения асинхронных операций.

**Требования к реализации:**
1.  **Generics:** Функция должна быть типизирована. Принимает `Promise<T>` или функцию, возвращающую Promise.
2.  **Состояние:** Возвращает `{ state, isLoading, error, execute }`.
3.  **AbortController:** При повторном вызове `execute` предыдущий запрос должен отменяться (если он еще идет).
4.  **Options:** Принимает опциональный параметр `immediate: boolean` (запускать ли сразу).
5.  **Типизация:** `state` может быть `T | null`.

**Шаблон для старта:**
```typescript
import { ref, shallowRef } from 'vue';

type AsyncStateOptions = {
  immediate?: boolean;
}

export function useAsyncState<T>(
  promiseFactory: (signal: AbortSignal) => Promise<T>,
  options: AsyncStateOptions = {}
) {
  // Ваш код здесь
}
```

---

## Самопроверка (Чек-лист)
- [ ] Я могу объяснить разницу между `Ref` и `Reactive` на уровне реализации JS Proxy.
- [ ] Я понимаю, почему в FSD нельзя импортировать виджет в виджет.
- [ ] Я написал типизированный `useAsyncState` с отменой запроса.
- [ ] Я знаю, как типизировать `provide/inject`.

Что делать с битыми ссылками в выдаче

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

Правильные ссылки проще всего найти самостоятельно в поисковике, но если не жалко токенов, можно попросить нейросеть исправить свою ошибку:

В плане на Блок 1 ты сгенерировал некорректную ссылку:

[FSD: Методология за 15 минут (Видео или статья)](https://habr.com/ru/articles/593575/)

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

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

*   **Материалы:**
    *   [FSD: Официальная документация (RU)](https://feature-sliced.design/ru/docs/get-started/overview)
    *   [FSD: Разбираемся с самой надёжной архитектурой (Habr)](https://habr.com/ru/companies/piter/articles/744824/)

А также извинился:

Пояснения насчёт битой ссылки
Извините за "битую" ссылку — видимо, я подставил ID другой статьи.

Я исправил файл `Блок_1_Vue_Architecture.md`, заменив ссылки на:
1.  **Официальную документацию FSD (на русском)** — это первоисточник, там всё изложено максимально точно.
2.  **Актуальную статью на Хабре** ("Разбираемся с FSD..."), которая хорошо объясняет концепцию.

Теперь материалы должны открываться корректно. Готовы переходить к следующему шагу или есть ещё вопросы по Блоку 1?

Обязательно ли добавлять контекст каждый раз?

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

  • чем дольше длится чат, тем сильнее растёт контекст и больше токенов тратится на каждый запрос, то есть запросы в длинном чате стоят немного дороже (но если это не десятки тысяч слов, то разница не критична)
  • в длинном чате выше вероятность, что нейросеть запутается и использует лишнюю информацию — например, если мы начнём строить план для другой вакансии в том же чате
  • в Cursor, как и в других редакторах, бывают баги, из-за которых часть переписки в чате может потеряться

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

Активный чат с агентом в Cursor и панель для нового чата справа

Активный чат с агентом в Cursor и панель агентов справа, где мы можем открыть новый чат кнопкой New Agent. В новом чате будет уже отдельный контекст

Читаем теорию и отвечаем на вопросы устно

Алгоритм следующий. Берём первую тему из плана на день. В моём примере, это «Реактивность в Vue 3». Читаем что-то по этой теме: в нашем случае в плане уже есть несколько ссылок на официальную документацию, но можно поискать дополнительные материалы. Смотрим и читаем всё это, пытаемся вникнуть в тему под разным углами.

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

Интерфейс диалога в Cursor с кнопкой голосового ввода

Нас интересует кнопка с микрофончиком справа

Выпадающее окно выбора языка голосового ввода в Cursor

Язык ввода можно выбрать через выпадающее меню, после того, как голосовой ввод включен. Чтобы настройка применилась, может понадобиться остановить ввод кнопкой «стоп» и начать его заново

Шаблон ответа можно начать примерно таким образом, нам важно, чтобы агент понял, на какой именно вопрос мы отвечаем:

Сейчас я расскажу про то, как устроена реактивность во Vue 3. Во Vue 3 реактивность основана на JavaScript Proxy вместо…

Надиктовываем ответ, он распознаётся и преобразуется в текст на лету. Ответ может быть довольно длинным, какие-то отдельные слова могут быть распознаны не совсем корректно, но в большинстве случаев это не играет роли, нейросеть нас и так поймёт. В конце нажимаем кнопку «стоп». Поскольку мы продолжаем диалог, начатый на предыдущих этапах, у агента уже есть весь необходимый контекст, и дополнительно можно ничего не прикреплять.

Отправляем ответ на проверку. Агент проанализирует его и выдаст оценку, отметит сильные и слабые стороны. В случае явных ошибок укажет на них. Разбор ответа может выглядеть сильно по-разному, но обратная связь в нём обязательно будет:

Разбор устного ответа нейросетью
Отличный, структурированный ответ. Ты покрыл базу: переход на `Proxy`, механизм `track/trigger`, решение проблем Vue 2 (добавление свойств, работа с коллекциями) и оптимизацию через `shallow`. Это уверенный уровень Middle+/Senior.

Чтобы ответ звучал на "Strong Senior", я бы добавил (или был готов ответить на доп. вопросы) пару нюансов:
1.  **Ref vs Proxy:** `ref` для примитивов технически работает через геттеры/сеттеры (класс `RefImpl`), так как примитив нельзя обернуть в Proxy. Proxy включается только когда в `ref` кладут объект.
2.  **WeakMap:** Зависимости хранятся в `WeakMap` (где ключ — сам объект `target`), чтобы не мешать сборщику мусора очищать память, когда объект больше не используется.

Давай проведем **блиц-проверку** по "скользким" моментам реактивности, на которых часто ловят:

**Вопрос 1 (Деструктуризация):**
У нас есть код:
```javascript
const state = reactive({ count: 0, user: { name: 'Alex' } });
const { count, user } = state;
```
Почему `count` потеряет реактивность при изменении, а `user.name` — нет (если изменить `state.user.name`)? И как правильно деструктурировать `state`, чтобы сохранить реактивность?

**Вопрос 2 (Unwrapping):**
Почему в шаблоне (template) мы пишем `{{ count }}`, а не `{{ count.value }}`, а в скрипте обязаны писать `.value`? И что произойдет, если я положу `ref` внутрь `reactive` объекта — нужно ли будет писать `.value` при доступе к нему?

Попробуй ответить на эти два вопроса. Это закроет теоретическую часть по реактивности.

За идеальной оценкой на этом этапе гнаться смысла нет, достаточно ответа «на четвёрку». Если мы не совсем довольны своим ответом, лучше сделать об этом пометку в каком-то отдельном файле и дополнительно попрактиковать эту тему позже.

Решаем практические задачи

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

Готовим интерактивную песочницу

Для задач на JS, где требуется написать какой-то код, можно использовать интерактивный режим в Node.js: создать пустой файл с названием test.js, открыть его в редакторе, открыть консоль в нижней панели редактора и запустить в консоли node --watch test.js. При каждом сохранении файла программа будет перезапускаться автоматически.

Решаем задачу в интерактивном режиме в Cursor. Окно чата закрыто

Решаем задачу в интерактивном режиме. Окно чата закрыто

Для задач на знание фреймворков удобнее развернуть отдельный тестовый проект на этом фреймворке. Например, для Vue.js можно создать проект через npm create vue@latest, установить зависимости, запустить его в dev-режиме и открыть папку с проектом в отдельном окне Cursor либо в каком-то другом редакторе кода.

Для задач на SQL лучше создать отдельную тестовую базу — например, новый SQLite-файл. Можно также попросить ИИ-агента создать в ней таблицы с определённой структурой и добавить тестовые данные. А после этого подключиться к базе через удобный клиент (я пользуюсь Beekeeper Studio) и практиковаться в нём.

Показываем решение агенту

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

Почему важно добавить контекст? Если у нас было достаточно много разных тем и какое-то количество похожих друг на друга задач, то нейросети может быть сложно понять, к какой конкретно задаче относится наше решение. Поэтому вместо того, чтобы объяснять, проще добавить ссылку на конкретную задачу в виде контекста. Чтобы это сделать, открываем файл с планом интересущего нас раздела, выделяем текст задачи и нажимаем кнопку Add to Chat либо Ctrl+L.

Всплывающий диалог для добавления контекста к чату в Cursor

Всплывающий диалог для добавления контекста к чату в Cursor

Прикреплённый контекст в окне чата Cursor

Прикреплённый контекст (строки 70-94 из файла) в окне чата Cursor

Отправляем. Нейросесть проанализирует код и выдаст оценку и рекомендации по его улучшению.

Разбор решения задачи нейросетью
Решение хорошее, демонстрирует понимание асинхронности и Composition API.

**Плюсы:**
1.  **AbortController:** Логика отмены реализована верно. Отменяется предыдущий, создается новый, и результат игнорируется, если сигнал `aborted`.
2.  **Типизация:** Использованы дженерики `<T>`, корректно типизирован `error`.
3.  **Race Conditions:** Обработка `isLoading` и игнорирование результата при отмене (`if (signal.aborted)`) защищает от состояния гонки.

**Что можно улучшить (Feedback для Senior уровня):**

1.  **Проблема с `Promise`:**
    В сигнатуре `PromiseOrFactory<T>` разрешен просто `Promise<T>`.
    ```typescript
    type PromiseOrFactory<T> = Promise<T> | ((signal: AbortSignal) => Promise<T>);
    ```
    *Проблема:* Если передать уже созданный `Promise` (не функцию), его **нельзя отменить** (у него нет сигнала) и его **нельзя перезапустить** (он уже зарезолвлен или в процессе). Метод `execute` при повторном вызове будет просто ждать тот же самый промис.
    *Рекомендация:* Для переиспользуемого `useAsyncState` лучше принимать *только* фабрику `() => Promise`. Если нужно обрабатывать "уже запущенный" промис, это другой кейс (обычно `usePromise`).
    *Как исправить:* Оставить только `(signal: AbortSignal) => Promise<T>` в аргументах.

2.  **Обработка `AbortError`:**
    ```typescript
    } catch (err) {
      if (signal.aborted) return; // ...
    ```
    Это правильный код, но стоит помнить, что `fetch` при отмене кидает специфическую ошибку `AbortError`. В некоторых реализациях хорошим тоном считается прокидывать ошибку дальше, если это *не* ошибка отмены. Но для UI-хука "молчаливое" игнорирование отмены (как у тебя) — это правильное поведение.

3.  **Утечка памяти (Memory Leak) в `onUnmounted`:**
    Если компонент демонтируется (пользователь ушел со страницы), а запрос висит — он продолжит висеть и потом попытается обновить `state.value`, что может (в редких случаях) привести к ворнингам или лишней работе.
    *Рекомендация:* Добавить автоматическую отмену при анмаунте.

    ```typescript
    import { onUnmounted } from 'vue';
    // ... внутри функции ...
    onUnmounted(() => {
      if (abortController) abortController.abort();
    });
    ```

4.  **ShallowRef:**
    Ты использовал `shallowRef` для `state`. Это **отлично** для производительности, если `state` это большой массив или сложный объект, который мы меняем целиком. Это показывает знание оптимизаций.

**Итоговая оценка:** 8/10. Если добавить `onUnmounted` и убрать поддержку "голого" Промиса (оставив только фабрику) — будет идеальное Senior-решение.

Хочешь доработать код с учетом `onUnmounted` или перейдем к вопросу про архитектуру (FSD) и задачу с деревом файлов?

Дополнительная практика в формате «блиц»

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

Придумай 5-6 небольших задач с подвохом для проверки понимания реактивности во Vue. Я хочу, чтобы ты предлагал мне по одной задаче, ждал моего ответа, оценивал его, и переходил к следующей задаче. По итогам хочу получить оценку моих ответов и рекомендации.

После чего агент будет выдавать задачи одну за одной:

Пример задачи, предложенной нейросетью
Принято. Это отличный способ закрепить нюансы. Буду давать задачи "с подвохом", которые часто встречаются на Senior собеседованиях.

### Задача 1: "Загадочный `watch`"

Представь, что у нас есть такой код компонента. Мы хотим отслеживать изменение свойства `count` в объекте `state`.

```typescript
const state = reactive({
  count: 0,
  data: { value: 10 }
});

// Вариант А
watch(state.count, (newVal) => {
  console.log('А сработал:', newVal);
});

// Вариант Б
watch(() => state.count, (newVal) => {
  console.log('Б сработал:', newVal);
});

state.count++;
```

**Вопрос:** Какой из вариантов (`А` или `Б`) сработает? Какой выдаст ошибку или предупреждение? Почему?

Агент, как правило, оценивает каждый из ответов сразу же перед тем, как задать следующий вопрос, а после того, как мы ответим на все вопросы, подготовит общую оценку:

Общая оценка блица
### Итоги по Блоку 1 (Vue Reactivity & Architecture)

**Твои результаты:**
1.  **Reactivity Fundamentals:** Отлично. Понимаешь Proxy, `track/trigger`.
2.  **Destructuring:** Отлично. Не путаешься в примитивах vs объектах.
3.  **Unwrapping (Ref in Reactive):** Есть пробел с коллекциями (`Map`, `Array`). Запомни: unwrap только в объектах по ключу.
4.  **Shallow APIs:** Отлично.
5.  **Readonly:** Отлично.

**Общая оценка:** Strong Senior (с маленькой помаркой по коллекциям, что простительно, так как редко используется).

**Рекомендация:**
Запомни кейс с `Map/Set` — это любят спрашивать, чтобы проверить глубину знаний ("а что если не просто объект, а Map?").

Перед собеседованием

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

На первичном созвоне с рекрутером как правило речь идёт о самой вакансии, о предыдущем опыте кандидата и об ожиданиях обеих сторон. Это обычно занимает 20-30 минут. Но если рекрутер планирует час и более на первичный созвон, скорее всего там будет блок технических вопросов, и скорее всего это будут вопросы в формате блиц, где предполагается быстрый ответ («для чего нужны замыкания в JS?») или вопросы, где надо посмотреть на код и быстро ответить, какой будет сайд-эффект («в каком порядке будут вызваны console.log и что будет выведено на экран»).

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

  • задачки на алгоритмы (говорят, их любит бигтех, а также некоторые компании, которые хотят быть, как бигтех)
  • поиск и исправление бага в коде
  • рефакторинг субъективно низкокачественного кода
  • одна большая задача с использованием фреймворка по принципу «сделай сколько успеешь в отведённое время»

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

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

После собеседования

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

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

- shared workers и другие способы коммуникации между вкладками в браузере
- какие бывают частые проблемы с вебсокетами и как они решаются
- <…>
  
Подготовь дополнительный модуль или несколько модулей по этим вопросам с практическими заданиями, чтобы я мог отработать слабые моменты и лучше подготовиться на будущее.

Таким образом, мы постепенно закрываем пробелы и выстраиваем систему подготовки, где к каждому следующему собеседованию мы будем готовы лучше, чем к предыдущему.

Удачи в поиске работы!