Спрячь и покажи: чистый фронтенд
Допустим, у вас есть тестовое задание.
Что вы с ним делаете?
В каком порядке что выполняете?
Сразу пишете код или проводите подготовку?
Какую?
В статье Мечтают ли джуны о тестовых заданиях автор (и наниматель) недоумевает:
Казалось бы, что может пойти не так? О, дважды наивный я! Для 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 (
);
}
components/checkbox.js
import React from 'react';
export default class Checkbox extends React.Component {
render() {
return (
);
}
}
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(
);
Если вам интересно, что делает одинокий импорт стилей из 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 (
);
}
}
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 и др). Объединение класса формы и класса темноты-цвета обеспечит вид в целом