Перейти к основному содержимому

Mock Junior Front End Web Developer Interview with Mike Chen and Silvia

· 22 мин. чтения

Сегодня мы разберём процесс проведения технического собеседования для позиции Junior Front-end разработчика, на примере совместной работы Сильвии и Майка. Мы увидим, как кандидат без профессионального опыта справляется с задачей по созданию компонентной структуры на React и вёрстке с использованием CSS Grid, а также разберём ключевые моменты взаимодействия, которые привели к успешному результату.

Вопрос 1. Расскажите о своём опыте и образовании. Что привело вас в разработку и что вы ищете в первой роли?

Таймкод: 00:01:28

Ответ собеседника: Правильный. По образованию экономист, работала в международных компаниях и проектах, где взаимодействовала с IT-специалистами и заинтересовалась программированием. В 2020 году была уволена в ходе реорганизации, параллельно родился ребёнок. Первоначальная идея — научиться кодить, чтобы стать IT-менеджером проектов, но после буткэмпа по front-end (JavaScript, React) в Барселоне влюбилась в программирование. Сейчас ищет первую работу в роли junior-разработчика.

Правильный ответ:

Ответ собеседника — это личная история, и она структурирована логично: образование → опыт → мотивация → переход в IT → цель. Это хороший формат для вводного рассказа на собеседовании.

Для подготовки к подобному вопросу стоит учитывать следующие рекомендации:

Структура ответа

Вводный рассказ на собеседовании — это ваш «elevator pitch». Оптимальная структура: кратко об образовании и предыдущем опыте → что привело в разработку → что изучили и какие проекты сделали → почему именно эта компания/роль.

Что ценит интервьюер

Интервьюер оценивает не столько факты биографии, сколько мотивацию, способность к самообучению и осознанность выбора. Упоминание буткэмпа, конкретных технологий и реальных проектов — это плюс.

Советы для кандидатов, меняющих профессию

Если вы приходите в разработку из другой сферы, подчёркивайте переносимые навыки: работа в команде, управление проектами, аналитическое мышление. Экономическое образование — это не минус, а плюс, если вы умеете применять аналитический подход к задачам.

Что можно усилить в ответе

Для позиции Golang-разработчика стоит явно упомянуть, почему выбор пал именно на Go, а не на другие языки. Например: интерес к бэкенду, микросервисам, высоконагруженным системам. Это покажет осознанность выбора стека.

Вопрос 2. Сколько карточек нужно отобразить из массива данных и будет ли дополнительное задание?

Таймкод: 00:05:38

Ответ собеседника: Правильный. Нужно отобразить первые шесть карточек из десяти, а если останется время — будет дополнительное расширение задачи.

Правильный ответ:

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

Рекомендации по выполнению подобных заданий

Приоритизация задач

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

Структура кода для отображения карточек

Пример на Go для работы с массивом данных:

type Card struct {
ID int
Title string
Image string
}

func getFirstNCards(cards []Card, n int) []Card {
if n > len(cards) {
return cards
}
return cards[:n]
}

// Использование
allCards := []Card{
{ID: 1, Title: "Card 1", Image: "img1.jpg"},
// ... остальные карточки
}
displayCards := getFirstNCards(allCards, 6)

Что учитывать при реализации

  • Проверяйте граничные условия: что если массив пустой, если элементов меньше чем N
  • Используйте константы для числовых значений (количество карточек)
  • Добавьте комментарии к коду, если это уместно для тестового задания

Работа с дополнительными заданиями

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

Вопрос 3. Как реализовать правильное склонение слова «year» (год/лет) в зависимости от числа?

Таймкод: 00:06:26

Ответ собеседника: Неполный. Было отмечено, что нужно учесть правильное склонение слова «year» в зависимости от числа (например, «one year» vs «two years»), но конкретная реализация не была предложена.

Правильный ответ:

Общая задача склонения слов (Pluralization)

Это классическая задача локализации и интернационализации (i18n/l10n). В английском языке правила проще, чем в русском, но тоже имеют исключения.

Правила для английского языка

В английском языке для слова «year» действуют простые правила:

  • 1 → year
  • Любое другое число → years

Простая реализация на Go

func formatYears(count int) string {
if count == 1 {
return "1 year"
}
return fmt.Sprintf("%d years", count)
}

Универсальная функция для множественных склонений

func pluralize(count int, singular, plural string) string {
if count == 1 {
return fmt.Sprintf("%d %s", count, singular)
}
return fmt.Sprintf("%d %s", count, plural)
}

// Использование
result := pluralize(1, "year", "years") // "1 year"
result := pluralize(5, "year", "years") // "5 years"

Для русского языка (сложнее)

Если задача включает русский язык, правила сложнее:

func pluralizeRU(count int, one, few, many string) string {
absCount := count
if absCount < 0 {
absCount = -absCount
}

remainder := absCount % 100
if remainder >= 11 && remainder <= 19 {
return fmt.Sprintf("%d %s", count, many)
}

lastDigit := remainder % 10
switch lastDigit {
case 1:
return fmt.Sprintf("%d %s", count, one)
case 2, 3, 4:
return fmt.Sprintf("%d %s", count, few)
default:
return fmt.Sprintf("%d %s", count, many)
}
}

// Использование
result := pluralizeRU(1, "год", "года", "лет") // "1 год"
result := pluralizeRU(2, "год", "года", "лет") // "2 года"
result := pluralizeRU(5, "год", "года", "лет") // "5 лет"
result := pluralizeRU(11, "год", "года", "лет") // "11 лет"
result := pluralizeRU(21, "год", "года", "лет") // "21 год"

Рекомендации

  • Для реальных проектов используйте библиотеки локализации (например, golang.org/x/text/message)
  • Всегда учитывайте граничные случаи: 0, отрицательные числа, большие числа
  • Тестируйте все варианты склонений

Вопрос 4. Нужно ли рендерить всё в React или можно использовать обычный JS? Можно ли создать отдельный компонент для карточки?

Таймкод: 00:06:52

Ответ собеседника: Правильный. Всё нужно рендерить в React. Создание отдельного компонента карточки — хорошая идея, чтобы передавать в него данные и маппить через массив.

Правильный ответ:

Ответ собеседника корректен. Использование React для рендеринга и создание отдельного компонента карточки — это правильный подход.

Пример реализации компонента карточки

// Card.jsx
function Card({ title, image, description }) {
return (
<div className="card">
<img src={image} alt={title} />
<h3>{title}</h3>
<p>{description}</p>
</div>
);
}

export default Card;

Использование компонента с маппингом массива

// CardList.jsx
import Card from './Card';

function CardList({ cards }) {
const displayCards = cards.slice(0, 6);

return (
<div className="card-list">
{displayCards.map(card => (
<Card
key={card.id}
title={card.title}
image={card.image}
description={card.description}
/>
))}
</div>
);
}

export default CardList;

Почему отдельный компонент — это правильно

  • Переиспользуемость: компонент Card можно использовать в разных частях приложения
  • Читаемость: код становится чище и легче поддерживать
  • Тестируемость: отдельный компонент проще тестировать изолированно
  • Принцип единственной ответственности: каждый компонент отвечает за одну вещь

Когда можно использовать обычный JS

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

Вопрос 5. С чего лучше начать — с рендеринга данных или со стилизации?

Таймкод: 00:08:31

Ответ собеседника: Правильный. Сначала реализовать функциональность и рендеринг данных, а затем заняться стилизацией.

Правильный ответ:

Ответ собеседника абсолютно верен. Это стандартная рекомендация в разработке: сначала функциональность, потом стилизация.

Рекомендуемый порядок работы

1. Структура данных

Определите структуру данных и подготовьте массив объектов для отображения.

const cards = [
{ id: 1, title: "Card 1", image: "img1.jpg", description: "Description 1" },
{ id: 2, title: "Card 2", image: "img2.jpg", description: "Description 2" },
// ... остальные карточки
];

2. Базовый рендеринг

Создайте компоненты и убедитесь, что данные корректно отображаются.

function App() {
const displayCards = cards.slice(0, 6);

return (
<div>
{displayCards.map(card => (
<div key={card.id}>
<h3>{card.title}</h3>
<p>{card.description}</p>
</div>
))}
</div>
);
}

3. Проверка функциональности

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

4. Стилизация

Только после того как функциональность работает, приступайте к CSS.

.card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
margin: 8px;
}

.card-list {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}

Почему такой порядок важен

  • Легче находить ошибки: если данные не отображаются, вы знаете, что проблема в логике, а не в стилях
  • Экономия времени: не нужно переделывать стили после изменения структуры компонентов
  • Демонстрация прогресса на собеседовании: интервьюер видит рабочую функциональность сразу

Вопрос 6. Поддерживает ли CodePen сокращения Emmet в JSX?

Таймкод: 00:09:08

Ответ собеседника: Правильный. Emmet работает в HTML-панели, но не в JSX. Можно написать HTML в HTML-панели и скопировать, заменив class на className, но это нецелесообразно.

Правильный ответ:

Ответ собеседника корректен. Emmet в CodePen работает только для HTML, а не для JSX.

Особенности Emmet в CodePen

CodePen поддерживает Emmet сокращения только в HTML-панели. В JSX-панели (JavaScript с Babel) Emmet не работает.

Обходной путь

Если вы хотите использовать Emmet для JSX:

  1. Напишите HTML-структуру в HTML-панели с помощью Emmet
  2. Скопируйте результат
  3. Вставьте в JSX-компонент
  4. Замените class на className, for на htmlFor и другие атрибуты

Альтернативы

Для более комфортной разработки на React рекомендуется использовать:

  • VS Code с расширением Emmet (поддерживает JSX из коробки)
  • CodeSandbox — онлайн-редактор с полной поддержкой React и Emmet
  • StackBlitz — ещё один онлайн-редактор с хорошей поддержкой React

Пример Emmet в VS Code для JSX

В VS Code с настроенным Emmet для JSX можно написать:

.card-list>.card*6>img+h3+p

И получить:

<div className="card-list">
<div className="card">
<img src="" alt="" />
<h3></h3>
<p></p>
</div>
// ... ещё 5 карточек
</div>

Вопрос 7. Где лучше выполнять маппинг данных — внутри JSX (в return) или в теле компонента перед return?

Таймкод: 00:13:38

Ответ собеседника: Правильный. Лучше выполнять маппинг непосредственно внутри JSX (в return), а не создавать отдельный массив JSX в теле компонента.

Правильный ответ:

Ответ собеседника верен. Маппинг внутри JSX — это более идиоматичный подход в React.

Сравнение двух подходов

Подход 1: Маппинг внутри JSX (рекомендуется)

function CardList({ cards }) {
return (
<div className="card-list">
{cards.slice(0, 6).map(card => (
<Card key={card.id} {...card} />
))}
</div>
);
}

Подход 2: Маппинг перед return

function CardList({ cards }) {
const displayCards = cards.slice(0, 6).map(card => (
<Card key={card.id} {...card} />
));

return (
<div className="card-list">
{displayCards}
</div>
);
}

Когда каждый подход уместен

Маппинг внутри JSX предпочтителен, когда логика простая и не загромождает return.

Маппинг перед return лучше использовать, когда:

function CardList({ cards, filter, sortBy }) {
// Сложная логика обработки данных
const processedCards = cards
.filter(card => card.category === filter)
.sort((a, b) => a[sortBy] - b[sortBy])
.slice(0, 6);

const displayCards = processedCards.map(card => (
<Card key={card.id} {...card} />
));

return (
<div className="card-list">
{displayCards}
</div>
);
}

Рекомендации

  • Для простых случаев — маппинг внутри JSX
  • Для сложной логики обработки — выносите в отдельные переменные или функции
  • Для очень сложных преобразований — используйте useMemo для оптимизации
function CardList({ cards, filter }) {
const displayCards = useMemo(() => {
return cards
.filter(card => card.category === filter)
.slice(0, 6)
.map(card => <Card key={card.id} {...card} />);
}, [cards, filter]);

return <div className="card-list">{displayCards}</div>;
}

Вопрос 8. Как передавать данные в компонент — передавать весь объект или отдельные свойства? Как деструктурировать пропсы?

Таймкод: 00:15:59

Ответ собеседника: Правильный. Можно передать весь объект cat как проп, а затем деструктурировать его свойства (name, breed, URL, description, age) в компоненте. Деструктуризацию лучше выполнять сразу в параметрах функции, а не через props.

Правильный ответ:

Ответ собеседника корректен. Деструктуризация в параметрах функции — это современный и предпочтительный подход.

Способы деструктуризации пропсов

Способ 1: Деструктуризация в параметрах (рекомендуется)

function Card({ name, breed, imageUrl, description, age }) {
return (
<div className="card">
<img src={imageUrl} alt={name} />
<h3>{name}</h3>
<p>{breed}</p>
<p>{description}</p>
<p>{age} years old</p>
</div>
);
}

Способ 2: Деструктуризация в теле компонента

function Card(props) {
const { name, breed, imageUrl, description, age } = props;

return (
<div className="card">
<img src={imageUrl} alt={name} />
<h3>{name}</h3>
</div>
);
}

Способ 3: Передача всего объекта

// Родительский компонент
function CardList({ cats }) {
return (
<div>
{cats.map(cat => (
<Card key={cat.id} cat={cat} />
))}
</div>
);
}

// Дочерний компонент
function Card({ cat }) {
return (
<div className="card">
<img src={cat.imageUrl} alt={cat.name} />
<h3>{cat.name}</h3>
</div>
);
}

Когда какой подход использовать

  • Деструктуризация в параметрах — для простых компонентов с небольшим количеством пропсов
  • Передача объекта — когда много свойств или они логически группируются
  • Rest-оператор для передачи оставшихся пропсов:
function Card({ name, breed, ...rest }) {
return (
<div className="card" {...rest}>
<h3>{name}</h3>
<p>{breed}</p>
</div>
);
}

Значения по умолчанию

function Card({ name, breed = "Unknown", age = 0 }) {
return (
<div className="card">
<h3>{name}</h3>
<p>{breed}</p>
<p>{age} years old</p>
</div>
);
}

Вопрос 9. Как исправить ошибку при передаче объекта cat в компонент Card, если деструктуризация настроена неправильно?

Таймкод: 00:20:48

Ответ собеседника: Неполный. Кандидат не смог самостоятельно предложить альтернативный способ исправления. Было подсказано, что можно использовать spread-оператор ({...cat}) при передаче пропсов, чтобы распаковать свойства объекта cat напрямую в компонент, либо деструктурировать вложенный объект cat из пропсов.

Правильный ответ:

Проблема

Типичная ошибка: компонент ожидает отдельные пропсы, но получает объект.

// Компонент ожидает отдельные пропсы
function Card({ name, breed, imageUrl }) {
return <div>{name} - {breed}</div>;
}

// А передаётся объект
const cat = { name: "Whiskers", breed: "Siamese", imageUrl: "cat.jpg" };

// Ошибка: пропсы не совпадают
<Card cat={cat} />
// Внутри компонента: props.cat.name, а не props.name

Способ 1: Spread-оператор (рекомендуется)

// Распаковка свойств объекта в пропсы
<Card {...cat} />

// Эквивалентно:
<Card name={cat.name} breed={cat.breed} imageUrl={cat.imageUrl} />

Способ 2: Деструктуризация вложенного объекта

// Изменить компонент для приёма объекта cat
function Card({ cat }) {
return (
<div className="card">
<h3>{cat.name}</h3>
<p>{cat.breed}</p>
</div>
);
}

// Или деструктурировать в теле компонента
function Card(props) {
const { name, breed, imageUrl } = props.cat;

return (
<div className="card">
<h3>{name}</h3>
<p>{breed}</p>
</div>
);
}

Способ 3: Деструктуризация в параметрах с вложенным объектом

function Card({ cat: { name, breed, imageUrl } }) {
return (
<div className="card">
<h3>{name}</h3>
<p>{breed}</p>
</div>
);
}

Сравнение подходов

ПодходПлюсыМинусы
{...cat}Просто, чистоМного пропсов на одном уровне
cat={cat}Группировка данныхНужно обращаться через cat.
Деструктуризация в параметрахЯвные имена пропсовСложнее при вложенных объектах

Рекомендация

Используйте spread-оператор, когда структура объекта совпадает с ожидаемыми пропсами компонента. Это самый чистый и читаемый подход.

Вопрос 10. Как ограничить отображение только первыми шестью карточками из массива?

Таймкод: 00:26:55

Ответ собеседника: Правильный. Использовать проверку индекса в маппинге — рендерить карточку только если индекс меньше 6 (index < 6), чтобы отобразить первые шесть элементов массива.

Правильный ответ:

Ответ собеседника корректен. Проверка индекса — один из рабочих способов.

Способ 1: Проверка индекса (предложен кандидатом)

function CardList({ cards }) {
return (
<div className="card-list">
{cards.map((card, index) => (
index < 6 && <Card key={card.id} {...card} />
))}
</div>
);
}

Способ 2: Метод slice (рекомендуется)

function CardList({ cards }) {
const displayCards = cards.slice(0, 6);

return (
<div className="card-list">
{displayCards.map(card => (
<Card key={card.id} {...card} />
))}
</div>
);
}

Способ 3: Фильтр по индексу

function CardList({ cards }) {
return (
<div className="card-list">
{cards.filter((_, index) => index < 6).map(card => (
<Card key={card.id} {...card} />
))}
</div>
);
}

Сравнение подходов

СпособПлюсыМинусы
index < 6Простой, один проходМенее читаемый, продолжает итерацию
slice(0, 6)Чистый, понятныйСоздаёт новый массив
filter + mapЯвная фильтрацияДва прохода по массиву

Рекомендация

Метод slice предпочтителен, потому что:

  • Код более читаемый и самодокументируемый
  • Не создаёт лишних итераций
  • Легко изменить количество отображаемых элементов
const DISPLAY_COUNT = 6;

function CardList({ cards }) {
return (
<div className="card-list">
{cards.slice(0, DISPLAY_COUNT).map(card => (
<Card key={card.id} {...card} />
))}
</div>
);
}

Вопрос 11. Какую технологию выбрать для раскладки карточек — Flexbox или CSS Grid? Каковы преимущества Grid?

Таймкод: 00:29:55

Ответ собеседника: Правильный. CSS Grid предпочтительнее для данной задачи, так как он позволяет легко управлять количеством колонок и адаптировать раскладку под разные размеры экрана без медиа-запросов. Flexbox тоже работает с wrap, но Grid даёт больше контроля и гибкости для адаптивных сеток.

Правильный ответ:

Ответ собеседника полный и корректный. CSS Grid действительно лучше подходит для сеток карточек.

CSS Grid для карточек

.card-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 24px;
padding: 16px;
}

Преимущества Grid

  • Двумерное управление: контроль и строк, и колонок одновременно
  • Адаптивность без медиа-запросов: auto-fill и auto-fit автоматически подстраивают количество колонок
  • Gap: встроенные отступы между элементами без margin-хаков
  • Выравнивание: мощные инструменты выравнивания по обеим осям

Flexbox для карточек

.card-list {
display: flex;
flex-wrap: wrap;
gap: 24px;
}

.card {
flex: 1 1 300px;
max-width: 400px;
}

Когда использовать что

ЗадачаЛучший выбор
Сетка карточекGrid
Навигация, кнопки в рядFlexbox
Выравнивание по центруFlexbox
Сложные макетыGrid
Одномерный списокFlexbox

Продвинутый пример Grid с адаптивностью

.card-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
gap: clamp(16px, 2vw, 32px);
}

Разница между auto-fill и auto-fit

/* auto-fill: создаёт пустые колонки */
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));

/* auto-fit: растягивает элементы на всю ширину */
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));

Вопрос 12. Можно ли использовать Google во время интервью для поиска синтаксиса или решения проблем?

Таймкод: 00:31:13

Ответ собеседника: Правильный. Да, во время интервью разрешено использовать Google для поиска любой информации, включая синтаксис CSS Grid или другие технические детали. Это не считается нарушением.

Правильный ответ:

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

Что можно искать на собеседовании

  • Синтаксис языка или фреймворка
  • Названия методов или свойств
  • Документацию по API
  • Примеры использования библиотек

Что не стоит искать

  • Готовые решения именно вашего тестового задания
  • Копировать целые блоки кода без понимания
  • Искать ответы на теоретические вопросы, которые проверяют ваши знания

Как правильно использовать поиск

  • Говорите вслух, что и зачем ищете
  • Объясняйте найденное решение
  • Адаптируйте найденный код под свою задачу
  • Не копируйте код вслепую — интервьюер может спросить, как он работает

Полезные ресурсы

  • MDN Web Docs — документация по HTML, CSS, JavaScript
  • React Docs — официальная документация React
  • Can I Use — проверка поддержки браузерами
  • CSS-Tricks — статьи и руководства по CSS

Важно помнить

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

Вопрос 13. Как ограничить ширину контейнера карточек и почему возникает горизонтальная прокрутка?

Таймкод: 00:33:53

Ответ собеседния: Неполный. Предложено задать max-width контейнеру (например, 800px), чтобы избежать горизонтальной прокрутки. Однако причина прокрутки до конца не была выявлена и устранена — кандидат предположил, что знает причину, но не озвучил её явно.

Правильный ответ:

Причины горизонтальной прокрутки

Горизонтальная прокрутка обычно возникает из-за:

1. Элементов, выходящих за пределы viewport

/* Фиксированная ширина, превышающая экран */
.card {
width: 1200px; /* Может быть шире экрана */
}

2. Отрицательных margin или padding

.card-list {
margin: 0 -20px; /* Выходит за пределы контейнера */
}

3. Абсолютного позиционирования

.card {
position: absolute;
right: -50px; /* Элемент за пределами экрана */
}

4. Длинного текста без переноса

.card-title {
white-space: nowrap; /* Текст не переносится */
}

Решение

/* Сброс и базовые настройки */
* {
box-sizing: border-box;
}

body {
overflow-x: hidden; /* Скрыть горизонтальную прокрутку */
}

/* Контейнер с ограничением */
.card-list-container {
max-width: 1200px;
margin: 0 auto;
padding: 0 16px;
}

/* Карточки */
.card-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 24px;
}

.card {
max-width: 100%;
overflow: hidden;
}

.card img {
max-width: 100%;
height: auto;
}

Как диагностировать проблему

В DevTools браузера:

  1. Откройте вкладку Elements
  2. Наведите на элементы — они подсвечиваются
  3. Найдите элемент, который выходит за пределы viewport
  4. Проверьте свойства width, margin, padding, position

Универсальный сброс для предотвращения прокрутки

html, body {
overflow-x: hidden;
width: 100%;
}

img, video, iframe {
max-width: 100%;
height: auto;
}

Вопрос 14. Как настроить CSS Grid с тремя колонками и отступами между карточками?

Таймкод: 00:36:07

Ответ собеседника: Правильный. Использовать grid-template-columns для создания трёх колонок и свойство gap (или column-gap и row-gap) для задания отступов между карточками. Также можно использовать сокращённое свойство gap для одновременной установки отступов по строкам и столбцам.

Правильный ответ:

Ответ собеседника полный и корректный.

Базовая настройка Grid с тремя колонками

.card-list {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}

Различные варианты задания отступов

/* Один gap для строк и столбцов */
.card-list {
gap: 24px;
}

/* Разные отступы для строк и столбцов */
.card-list {
column-gap: 24px;
row-gap: 16px;
}

/* Сокращённая запись: row-gap column-gap */
.card-list {
gap: 16px 24px;
}

Адаптивная сетка с тремя колонками

.card-list {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
max-width: 1200px;
margin: 0 auto;
padding: 16px;
}

/* Планшет: 2 колонки */
@media (max-width: 768px) {
.card-list {
grid-template-columns: repeat(2, 1fr);
}
}

/* Мобильный: 1 колонка */
@media (max-width: 480px) {
.card-list {
grid-template-columns: 1fr;
}
}

Современный подход без медиа-запросов

.card-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: clamp(16px, 2vw, 32px);
max-width: 1200px;
margin: 0 auto;
padding: 16px;
}

Пример полной стилизации карточки

.card {
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
transition: transform 0.2s ease;
}

.card:hover {
transform: translateY(-4px);
}

.card img {
width: 100%;
height: 200px;
object-fit: cover;
}

.card-content {
padding: 16px;
}

.card-title {
font-size: 18px;
font-weight: 600;
margin: 0 0 8px;
}

.card-description {
font-size: 14px;
color: #666;
margin: 0;
}

Вопрос 15. Как стилизовать карточки: размер шрифта имени, цвет текста породы/возраста, описание, паддинги и границы?

Таймкод: 00:38:21

Ответ собеседника: Правильный. Имя кота — увеличенный шрифт (например, 20px), порода и возраст — серый цвет (например, #777), описание — обычный стиль. Добавить padding внутри карточки и border (1px solid) для рамки вокруг карточки.

Правильный ответ:

Ответ собеседника корректен. Это стандартный подход к стилизации карточек.

Полный пример стилей карточки

.card {
background: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transition: box-shadow 0.2s ease, transform 0.2s ease;
}

.card:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
transform: translateY(-2px);
}

.card-image {
width: 100%;
height: 200px;
object-fit: cover;
}

.card-content {
padding: 16px;
}

.card-name {
font-size: 20px;
font-weight: 600;
color: #333;
margin: 0 0 8px;
}

.card-breed {
font-size: 14px;
color: #777;
margin: 0 0 4px;
}

.card-age {
font-size: 14px;
color: #777;
margin: 0 0 12px;
}

.card-description {
font-size: 14px;
color: #555;
line-height: 1.5;
margin: 0;
}

Рекомендации по стилизации

Типографика

  • Используйте чёткую иерархию размеров: имя > описание > вторичная информация
  • Межстрочный интервал (line-height) для текста: 1.4–1.6
  • Ограничивайте длину строки для читаемости

Цвета

  • Основной текст: #333 или #222
  • Вторичный текст: #666 или #777
  • Акцентные цвета для ссылок или кнопок

Отступы

  • Внутренние отступы (padding): 16px — стандарт
  • Между элементами: 8px, 12px, 16px
  • Используйте систему отступов: 4px, 8px, 12px, 16px, 24px, 32px

Границы и тени

/* Тонкая граница */
border: 1px solid #e0e0e0;

/* Скруглённые углы */
border-radius: 8px;

/* Тень для объёма */
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);

CSS-переменные для консистентности

:root {
--color-text-primary: #333;
--color-text-secondary: #777;
--color-border: #e0e0e0;
--spacing-sm: 8px;
--spacing-md: 16px;
--border-radius: 12px;
}

.card-name {
color: var(--color-text-primary);
margin-bottom: var(--spacing-sm);
}

.card-breed {
color: var(--color-text-secondary);
}

Вопрос 16. Как использовать DevTools для отладки сетки и проверки точности gap в CSS Grid?

Таймкод: 00:46:51

Ответ собеседника: Правильный. Навести курсор на элемент grid в DevTools, чтобы визуально выделить сетку и проверить точность отступов (gap). Это помогает убедиться, что раскладка соответствует макету.

Правильный ответ:

Ответ собеседника корректен. DevTools — мощный инструмент для отладки CSS Grid.

Как включить визуализацию Grid в DevTools

Chrome DevTools:

  1. Откройте DevTools (F12 или Cmd+Option+I)
  2. Перейдите на вкладку Elements
  3. Найдите элемент с display: grid
  4. В дереве DOM рядом с элементом появится значок grid
  5. Нажмите на этот значок — сетка визуально выделится на странице

Настройка отображения Grid:

  1. Перейдите на вкладку Layout в DevTools
  2. В разделе Grid вы увидите все grid-контейнеры на странице
  3. Можно включить/выключить отображение линий и номеров треков
  4. Настроить цвет и стиль линий

Что можно проверить с помощью Grid overlay:

  • Количество колонок и строк
  • Размеры треков (fr, px, %)
  • Отступы (gap) между элементами
  • Расположение элементов в сетке
  • Именованные области (grid-template-areas)

Практические советы

/* Для отладки можно временно добавить */
.card-list {
outline: 2px dashed red;
}

.card {
outline: 1px solid blue;
}

Другие полезные инструменты DevTools

  • Computed — посмотрит вычисленные значения всех свойств
  • Box Model — визуализация margin, border, padding, content
  • Device Mode — проверка адаптивности на разных экранах

Firefox DevTools

Firefox также имеет отличные инструменты для работы с Grid:

  1. Откройте инспектор (F12)
  2. Найдите grid-контейнер
  3. Нажмите на значок grid рядом с display: grid
  4. Используйте панель Grid для настройки отображения

Вопрос 17. Что бы вы улучшили в коде, если бы это был реальный pull request? Как сделать код чище и поддерживаемее?

Таймкод: 00:49:03

Ответ собеседника: Правильный. Вынести данные в отдельный файл, разделить компоненты на отдельные файлы для лучшей структуры. Убрать условие внутри map для ограничения количества карточек — вместо этого использовать slice перед маппингом. Улучшить семантический HTML: использовать article для карточки, section для контейнера, h1 для имени, h2 для породы/возраста. Добавить адаптивность для мобильных устройств с помощью grid-template-columns и minmax. Задавать max-width: 100% для изображений по умолчанию, чтобы избежать растягивания сетки.

Правильный ответ:

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

Структура проекта

src/
├── components/
│ ├── Card.jsx
│ ├── CardList.jsx
│ └── CardList.css
├── data/
│ └── cats.js
├── App.jsx
└── index.js

Вынесение данных

// data/cats.js
export const cats = [
{
id: 1,
name: "Whiskers",
breed: "Siamese",
age: 3,
imageUrl: "https://example.com/cat1.jpg",
description: "A playful and curious cat."
},
// ... остальные коты
];

Семантический HTML

// CardList.jsx
function CardList({ cards }) {
const displayCards = cards.slice(0, 6);

return (
<section className="card-list-container" aria-label="Cat cards">
<h2>Our Cats</h2>
<div className="card-list">
{displayCards.map(card => (
<Card key={card.id} {...card} />
))}
</div>
</section>
);
}

// Card.jsx
function Card({ name, breed, age, imageUrl, description }) {
return (
<article className="card">
<img src={imageUrl} alt={name} className="card-image" />
<div className="card-content">
<h3 className="card-name">{name}</h3>
<p className="card-breed">{breed}</p>
<p className="card-age">{pluralize(age, "year", "years")}</p>
<p className="card-description">{description}</p>
</div>
</article>
);
}

Дополнительные улучшения

1. PropTypes для типизации

import PropTypes from 'prop-types';

Card.propTypes = {
name: PropTypes.string.isRequired,
breed: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
imageUrl: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
};

2. CSS-модули для изоляции стилей

import styles from './Card.module.css';

function Card({ name, imageUrl }) {
return (
<article className={styles.card}>
<img src={imageUrl} alt={name} className={styles.image} />
</article>
);
}

3. Lazy loading для изображений

<img
src={imageUrl}
alt={name}
loading="lazy"
className="card-image"
/>

4. Accessibility

<section aria-label="Cat cards">
<h2 id="cats-heading">Our Cats</h2>
<div role="list" aria-labelledby="cats-heading">
{displayCards.map(card => (
<div role="listitem" key={card.id}>
<Card {...card} />
</div>
))}
</div>
</section>

5. Обработка ошибок загрузки изображений

function Card({ name, imageUrl }) {
const [imgSrc, setImgSrc] = useState(imageUrl);

const handleError = () => {
setImgSrc('/placeholder-cat.jpg');
};

return (
<img
src={imgSrc}
alt={name}
onError={handleError}
className="card-image"
/>
);
}