Допустим, у вас есть тестовое задание.
Что вы с ним делаете?
В каком порядке что выполняете?
Сразу пишете код или проводите подготовку?
Какую?
В статье Мечтают ли джуны о тестовых заданиях автор (и наниматель) недоумевает:
Казалось бы, что может пойти не так? О, дважды наивный я! Для 90% кандидатов эта задача оказалась то ли слишком сложна, то ли слишком скучна, то ли всё сразу: кто-то путал цвета, приравнивая синий к зелёному, а тёмный к красному.
Кто-то использовал copy-paste там, где можно было пройтись циклом по массиву, кто-то не осилил прочитать данные из json, нагородив плетень из запрошенных технологий (увы, не работающий корректно). Отступы? Ну, как c SO скопировалось, такие и отступы! Многие, как выяснилось, не понимают, чем checkbox отличается от radio - если верить резюме, фронтендеры с опытом работы год-два. * facepalm *
Прямая ссылка на тестовое от @mSnus на гитхабе вот. А вот
картинка из тестового

В тестовом две картинки. Одна с раскрытым меню (вот эта), другая - с закрытым.
Ниже обсудим, как это тестовое выполнить аккуратно, пошагово. Ну и - побочный эффект - относительно быстро.
Пишем план по написанию плана
Я совершенно серьёзно. Чтобы понять, в каком порядке что кодить, надо сначала выяснить, что мы вообще тут видим. Что от нас вообще требуется в итоге. Кроме того, понять, что мы уже умеем на все 100%, а что, возможно, стоит ещё загуглить и повторить.
Бывает, что гуглить бессмысленно - это когда зазор между нашими знаниями и требованиями избыточен. Такой зазор мы не сможем преодолеть в один беглый загугл. Т.е., возможно, пока что эта должность - чужая.
Бывает и так, что зазор этот есть, но мелкий. Скажем, вы видите, что нужен axios (и в нашем тестовом он как раз нужен). Вы с axios немного работали, но забыли. Можно зайти на документацию axios на гитхабе, или в ютуб заглянуть, посмотреть пару видео - как вам удобнее.
Коротко говоря, до написания плана по написанию кода, стоит составить план повторения материала. Что-то вы помните хорошо, что-то слабее. Что-то совсем не умеете - может быть, лучше тогда и не браться за тестовое, только время потратите (и ваше, и нанимателя - на проверку).
Ещё один тонкий момент - архитектура. Как именно вы будете реализовывать разные части вашего приложения? Не на уровне кода, а в целом: как разделить эту вёрстку на компоненты, как что должно реагировать - и на какие действия юзера?
Собственно, что вообще юзер способен делать на нашей вёрстке? В данном случае юзер может
скрыть и раскрыть меню,
переменить расположение галочек на чекбоксах (круги / квадраты - т.е., разные формы - в шапке сайта, и красные / зелёные / синие / жёлтые - т.е., цвета - в меню),
переменить выбор в радио-группе (все / тёмные / светлые, назовём это darkness),
переменить число в поле ввода "(число) колонок".
А как страница должна реагировать на действия юзера?
что-то должно прятаться и появляться (меню, фигурки на заднем плане, галочки внутри чекбоксов, чёрные круги внутри элементов радио-группы),
что-то должно сужаться и расширяться (фигурки на заднем плане, чтобы менялось число колонок с фигурками).
Выбор у нас по вёрстке, скажем, такой:
сделать чекбоксы через input type="checkbox" или как кнопки с картинками?
сделать радио-группу как input type="radio" или как кнопки с картинками?
Если мы сделаем их как button с вложенной SVG, можно будет точнее выполнить вёрстку. Кроме того, кнопку можно подписывать изнутри, что расширяет площадь кликабельного элемента - так по нему проще попасть.
Однако при выборе кнопок придётся прописывать скрытие / показ галочки / чёрного круга в кнопке вручную. Радио-группу придётся вручную объединять функционально. Кроме того, для электронных читалок кнопка - это другое.
Мне удобнее кнопки: в этом тестовом вёрстка ну очень миленькая. Я бы хотела полностью её реализовать. С картинками это проще.
Выпишите, какие ещё развилки вы видите в этом тестовом. Где есть варианты выбора, что выбираете вы, почему.
Конкретизируем основные детали
Чтобы что-нибудь кодить, надо решить, с чего мы начнём. Какие самостоятельные детали у нас бывают? Что тянет на компоненту, а что скорее частное применение компоненты? Скажем, как выполнить наши чекбоксы-кнопки: это сто пять (ну шесть, ок) отдельных чекбоксов, или один и тот же чекбокс с параметрами? Одна компонента с параметрами?
Приведу пример кнопки-чекбокса как одной компоненты с параметрами. Есть базовая кнопка с картинкой в векторной графике SVG. Внутри картинки внешний квадратик - элемент rect, а галочка - элемент polygon. Кроме того, есть текст по имени this.props.title.
Стили картинки мы задаём несколькими отдельными классами на уровне кнопки:
базовый класс чекбокса checkbox даёт серую рамку,
ховер-класс checkbox:hover задаёт поведение при наведении курсора,
цветные классы checkbox-red и др. задают фон квадратика.
На клики пока что чекбокс реагирует вяло - только выводит в консоль свой айдишник. Позже мы будем ему по клику изменять стиль, а также перерисовывать по клику фигурки на заднем фоне. Но это - позже. Пока что мы только начали, и следом аналогично добавим радио-группу.
Файлы, которые вам нужны: компонент components/checkbox.js и файл стилей чекбоксов scss/checkbox.scss. В файле App.js вызываем наш компонент со всеми наборами классов и с разными наборами параметров, чтобы проверить работу кнопки. Вот основные файлы на данном этапе:
App.js
import React from 'react'; import Checkbox from './components/checkbox'; export default function App() { return ( <div> <Checkbox title="круги" id="circle" classes="checkbox" /> <Checkbox title="квадраты" id="square" classes="checkbox" /> <Checkbox title="красные" id="red" classes="checkbox checkbox-red" /> <Checkbox title="зелёные" id="green" classes="checkbox checkbox-green" /> <Checkbox title="синие" id="blue" classes="checkbox checkbox-blue" /> <Checkbox title="жёлтые" id="yellow" classes="checkbox checkbox-yellow" /> </div> ); }
components/checkbox.js
import React from 'react'; export default class Checkbox extends React.Component { render() { return ( <button className={this.props.classes} onClick={() => console.log(this.props.id)} > <svg viewBox="0 0 100 100"> <rect x="10" y="10" width="80" height="80" /> <polygon points="10,30 50,50 80,0 50,80 10,30" /> </svg> {this.props.title} </button> ); } }
scss/checkbox.scss
.checkbox { display: flex; background: none; border: none; font-size: 2rem; svg { width: 2rem; margin-right: 0.5rem; rect { fill: none; stroke: $color-neutral; stroke-width: 5px; } } &:hover { cursor: pointer; svg rect { stroke: $color-dark; } } &-red svg rect { fill: $color-light-red; } &-green svg rect { fill: $color-light-green; } &-blue svg rect { fill: $color-light-blue; } &-yellow svg rect { fill: $color-light-yellow; } }
Сделайте аналогичную компоненту для радио-кнопки.
Прыг-скок
В тестовом требуется добавить Redux. Вопрос, куда именно. Что мы будем реализовывать через него? Что мы будем в нём запоминать? А как и где использовать эти данные?
Все наши кнопки-чекбоксы имеют id. Так что можно в Redux хранить состояние по id. Набор "circle: true, red: false и т.д." нам говорит, что, в частности, выбран чекбокс "круги", а чекбокс "красные", наоборот, не выбран.
Дальше используем эти данные для формирования className на галочке. Если в редаксе значение по ключу true, галочку должно быть видно. А если там false, надо внутри компонента галочке сделать класс hidden.
Собственно, hidden - это универсальный класс. Умеет ровно одно: прятать свой элемент. Нам же ещё понадобится прятать цветные фигурки на заднем плане.
Итак. Создадим наш store:
redux/reducer.js
export default function reducer(state = {}, action) { const newState = { ...state }; switch (action.type) { case 'SET': newState[action.id] = action.value; return newState; case 'TOGGLE': newState[action.id] = !state[action.id]; return newState; default: return newState; } }
redux/store.js
import { configureStore } from '@reduxjs/toolkit'; import reducer from './reducer.js'; export default configureStore({ reducer });
Импортируем его в корневом файле index.js и подключим к нашему приложению через Provider:
index.js
import './scss/index.scss'; import React, { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import App from './App'; import { Provider } from 'react-redux'; import store from './redux/store.js'; const rootElement = document.getElementById('root'); const root = createRoot(rootElement); root.render( <StrictMode> <Provider store={store}> <App /> </Provider> </StrictMode> );
Если вам интересно, что делает одинокий импорт стилей из index.scss тут вверху: весь scss можно складывать в одну папку scss отдельными файлами, по разным темам. Дальше мы импортируем их внутри папки scss в этот тамошний индекс scss-импортами. Ну а в js используем уже только только scss/index.scss.
Если бы приложение было побольше, я бы иначе организовала файлы. Были бы отдельные модули по разным темам, и в каждом модуле по отдельности и свои компоненты реакт, и свои стили. И подключала бы стили внутри компонентов.
А в компоненте законнектируем состояние из редакса с местными пропсами:
components/checkbox.js
import React from 'react'; import { connect } from 'react-redux'; import store from '../redux/store.js'; class Checkbox extends React.Component { constructor(props) { super(props); store.dispatch({ type: 'SET', id: this.props.id, value: true }); } render() { return ( <button className={this.props.classes} onClick={() => store.dispatch({ type: 'TOGGLE', id: this.props.id })} > <svg viewBox="0 0 100 100"> <rect x="10" y="10" width="80" height="80" /> <polygon points="10,30 50,50 80,0 50,80 10,30" className={this.props.redux[this.props.id] ? '' : 'hidden'} /> </svg> {this.props.title} </button> ); } } function mapStateToProps(state) { return { redux: state }; } export default connect(mapStateToProps)(Checkbox);
Мы научились запоминать в Redux выбор разных чекбоксов. Сами чекбоксы теперь умеют менять картинку: после клика по кнопке галочка пропадает, следующий клик по кнопке галочку возвращает.
Замечу, что при таком подходе у нас ни малейших шансов спутать красные с круглыми. Мы, вообще говоря, не знаем, где именно красные. Всем заведует компонента чекбокс, а для неё нет красных или квадратных, есть только данные id, title и classes, они просто строки.
Напишите аналогичное подключение для компонента радио.
подсказки
каждую кнопку радио в редаксе запоминать не требуется, ровно наоборот: стоит создать единственный ключ, вроде darkness, и для него менять значение на айдишник кнопки. Выбрано all - диспатчим {type: 'SET', id: 'darkness', value: 'all'}. И так далее.
чёрный кружок должен быть только в той кнопке, которая выбрана. А в двух других он hidden.
Где ещё конь не валялся
Собственно, нам остаётся
принести данные с сервера (это про axios),
почистить данные (prop-types в компонентах или что-то кастомное ещё на выходе axios),
отрисовать фигурки на заднем плане,
оформить число колонок,
сверстать общий layout,
добавить скрытие и раскрытие меню по клику на бургере слева сверху (и заодно по полю рядом с меню - просто скрытие)
Прикиньте и запишите, сколько каждый из этих этапов у вас займёт.
Теперь запишите рядом, сколько времени каждый этап займёт, если его выполнять отдельно - между другими делами - а не весь этот список подряд.
Что у вас получилось, какой вариант для вас проще, удобнее, по вашим прикидкам?
Проверьте. Сделайте три-четыре пункта подряд. Выполните оставшееся отдельными действиями. В какую сторону и в каком случае как отличается время? Насколько вы верно оценивали время заранее?
немного подсказок к коду
число колонок можно сделать добавочным классом на внешнем поле, в котором выведены цветные фигурки. Базовый класс - это флекс с переносом строки и равномерным распределением по горизонтали. А дополнительный класс - только число колонок. Скажем, cols-5 задаёт на вложенных элементах flex-basis: 20%
скрываться или показываться фигурка должна сама. Фигурка - квадрат или круг, красный и др., тёмный или же светлый - тоже отдельная компонента с параметрами. И компонента способна проверить, годятся ли её вводные под состояние, сохранённое внутри Redux
для фигурок имеет смысл завести отдельные классы формы (figure-circle, figure-square) и отдельные классы темноты с цветом (figure-dark-red, figure-light-blue и др). Объединение класса формы и класса темноты-цвета обеспечит вид в целом
