Допустим, у вас есть тестовое задание.
-
Что вы с ним делаете?
-
В каком порядке что выполняете?
-
Сразу пишете код или проводите подготовку?
-
Какую?
В статье Мечтают ли джуны о тестовых заданиях автор (и наниматель) недоумевает:
Казалось бы, что может пойти не так? О, дважды наивный я! Для 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 и др). Объединение класса формы и класса темноты-цвета обеспечит вид в целом
Автор: Мария