Спрячь и покажи: чистый фронтенд

в 20:48, , рубрики: javascript, ReactJS, самоорганизация, тестовое задание, фронтенд

Допустим, у вас есть тестовое задание.

  • Что вы с ним делаете?

  • В каком порядке что выполняете?

  • Сразу пишете код или проводите подготовку?

  • Какую?

В статье Мечтают ли джуны о тестовых заданиях автор (и наниматель) недоумевает:

Казалось бы, что может пойти не так? О, дважды наивный я! Для 90% кандидатов эта задача оказалась то ли слишком сложна, то ли слишком скучна, то ли всё сразу: кто-то путал цвета, приравнивая синий к зелёному, а тёмный к красному.

Кто-то использовал copy-paste там, где можно было пройтись циклом по массиву, кто-то не осилил прочитать данные из json, нагородив плетень из запрошенных технологий (увы, не работающий корректно). Отступы? Ну, как c SO скопировалось, такие и отступы! Многие, как выяснилось, не понимают, чем checkbox отличается от radio - если верить резюме, фронтендеры с опытом работы год-два. * facepalm *

Прямая ссылка на тестовое от @mSnus на гитхабе вот. А вот

картинка из тестового
Спрячь и покажи: чистый фронтенд - 1

В тестовом две картинки. Одна с раскрытым меню (вот эта), другая - с закрытым.

Ниже обсудим, как это тестовое выполнить аккуратно, пошагово. Ну и - побочный эффект - относительно быстро.


Пишем план по написанию плана

Я совершенно серьёзно. Чтобы понять, в каком порядке что кодить, надо сначала выяснить, что мы вообще тут видим. Что от нас вообще требуется в итоге. Кроме того, понять, что мы уже умеем на все 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 и др). Объединение класса формы и класса темноты-цвета обеспечит вид в целом

Автор: Мария

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js