├── 01.extend-extends.jsx ├── 02.no-super.jsx ├── 03.async-state.jsx ├── 04.jsx-syntax.jsx ├── 05-event-handler-call.jsx ├── 06-event-bind.jsx ├── 07-event-capture.jsx ├── 08-event-bind-why.jsx ├── 09.wrong-state-write.jsx ├── 10.wrong-ref.jsx ├── README.md └── templates ├── 01.button-count.jsx ├── 02.button-count-redhot.jsx ├── 03.form-dom-validity.jsx ├── 04.form-validity.jsx └── README.md /01.extend-extends.jsx: -------------------------------------------------------------------------------- 1 | // Вопрос: почему не работает код 2 | 3 | class Button extend React.Component { 4 | constructor(props) { 5 | super(props); 6 | this.state = { 7 | clicked: 0 8 | }; 9 | } 10 | 11 | render() { 12 | return ; 13 | } 14 | 15 | onClick() { 16 | this.setState({ 17 | clicked: this.state.clicked + 1 18 | }); 19 | } 20 | } 21 | 22 | ReactDOM.render(, document.querySelector("#app")) 23 | 24 | // Варианты ответа 25 | // 1. Неправильный синтаксис наследования класса [правильный ответ: нужно использовать extend] 26 | // 2. Нужно наследоваться от ReactComponent, а не от React.ReactComponent 27 | // 3. Неправильно используется ReactDOM.render: нужно поменять порядок аргументов 28 | // 4. Нельзя инициализировать состояние в конструкторе: нужно создавать его на основе props 29 | -------------------------------------------------------------------------------- /02.no-super.jsx: -------------------------------------------------------------------------------- 1 | // Вопрос: почему не работает код 2 | 3 | class Button extends React.Component { 4 | constructor(props) { 5 | this.state = { 6 | clicked: 0 7 | }; 8 | } 9 | 10 | render() { 11 | return ; 12 | } 13 | 14 | onClick() { 15 | this.setState({ 16 | clicked: this.state.clicked + 1 17 | }); 18 | } 19 | } 20 | 21 | ReactDOM.render(, document.querySelector("#app")) 22 | 23 | // Варианты ответа 24 | // 1. Неправильная инициализация класса: в конструкторе нужно вызвать super родительского метода [правильный ответ] 25 | // 2. Нельзя называть класс button: такой класс уже есть в JavaScript DOM API 26 | // 3. ReactDOM.render нужно вызывать после DOMContentLoaded 27 | // 4. Проблема в onClick 28 | -------------------------------------------------------------------------------- /03.async-state.jsx: -------------------------------------------------------------------------------- 1 | // Вопрос: кнопка станет красной... 2 | 3 | class Button extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | this.state = { 7 | clicked: 0, 8 | redHot: false, 9 | }; 10 | } 11 | 12 | render() { 13 | return ; 16 | } 17 | 18 | onClick() { 19 | this.setState({ 20 | clicked: this.state.clicked + 1 21 | }); 22 | 23 | if (this.state.clicked >= 10) { 24 | this.setState({ 25 | redHot: true, 26 | }); 27 | } 28 | } 29 | } 30 | 31 | ReactDOM.render(, document.querySelector("#app")) 32 | 33 | // Варианты ответа: 34 | // 1. Не раньше одиннадцатого нажатия [правильный ответ] [Cтейт обновляется асинхронно, 35 | // а тут не используется коллбэк, поэтому кнопка станет красной, 36 | // но не раньше 11 нажатия] 37 | // 2. Ровно при десятом нажатии 38 | // 3. Нет, не станет, в коде ошибка: лишняя запятая при создании объекта state 39 | // 4. Нет, не станет, в коде ошибка: нельзя так работать со стилями 40 | -------------------------------------------------------------------------------- /04.jsx-syntax.jsx: -------------------------------------------------------------------------------- 1 | // Вопрос: ну теперь-то всё хорошо? 2 | 3 | class Button extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | this.state = { 7 | clicked: 0, 8 | redHot: false, 9 | }; 10 | } 11 | 12 | render() { 13 | return ; 16 | } 17 | 18 | onClick() { 19 | this.setState({ 20 | clicked: this.state.clicked + 1 21 | }, () => { 22 | this.setState({ 23 | redHot: this.state.clicked >= 10, 24 | }); 25 | }); 26 | } 27 | } 28 | 29 | ReactDOM.render(, document.querySelector("#app")) 30 | 31 | // Варианты ответа: 32 | // 1. Нет, вот теперь неправильно задаётся стиль. [правильный ответ][Синтаксис JSX работает так, 33 | // что если я хочу передать объект как аргумент для React-элемента или компонента, 34 | // то мне нужно будет написать две пары фигурных скобок {{ background: `red` }}. 35 | // Внешние фигурные скобки обозначают, что сейчас будет вставлено обычное JS- 36 | // значение, а внутренние - это литерал объекта, который я передаю] 37 | // 2. Всё будет работать 38 | // 3. Счётчик должен начинаться с единицы 39 | // 4. В условии должно быть написано не this.state.clicked >= 10, а this.state.clicked === 10 40 | -------------------------------------------------------------------------------- /05-event-handler-call.jsx: -------------------------------------------------------------------------------- 1 | // Вопрос: Что не так с этим кодом? 2 | 3 | class Form extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | this.state = { 7 | errorMessage: '', 8 | }; 9 | } 10 | 11 | render() { 12 | return
; 19 | } 20 | 21 | onInput() { 22 | this.setState({ errorMessage: '' }); 23 | } 24 | 25 | onInvalid(evt) { 26 | evt.preventDefault(); 27 | this.setState({ 28 | errorMessage: evt.target.name === 'fullname' 29 | ? 'Введите имя, это обязательное поле' 30 | : 'Введите возраст. Возраст не должен быть меньше 18 лет' 31 | }); 32 | } 33 | } 34 | 35 | ReactDOM.render(, document.querySelector("#app")); 36 | 37 | // Варианты ответа 38 | // 1. Обработчики не добавляются, а вызываются, нужно убрать круглые скобки [правильный ответ] 39 | // 2. С этим кодом всё хорошо 40 | // 3. В onInput и onInvalid нужно передавать this.props, а не this, так работает жизненный цикл компонент 41 | // 4. Неправильная проверка пустого состояния errorMessage в методе render. Чтобы этот код работал, errorMessage должен быть равен null 42 | -------------------------------------------------------------------------------- /06-event-bind.jsx: -------------------------------------------------------------------------------- 1 | // Вопрос: Что не так с этим кодом? 2 | 3 | class Form extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | this.state = { 7 | errorMessage: '', 8 | }; 9 | } 10 | 11 | render() { 12 | return ; 19 | } 20 | 21 | onInput() { 22 | this.setState({ errorMessage: '' }); 23 | } 24 | 25 | onInvalid(evt) { 26 | evt.preventDefault(); 27 | this.setState({ 28 | errorMessage: evt.target.name === 'fullname' 29 | ? 'Введите имя, это обязательное поле' 30 | : 'Введите возраст. Возраст не должен быть меньше 18 лет' 31 | }); 32 | } 33 | } 34 | 35 | ReactDOM.render(, document.querySelector("#app")); 36 | 37 | // Варианты ответа 38 | // 1. Код обработчиков выполняется с неправильным контекстом [правильный ответ] 39 | // [Контекст выполнения обработчиков всегда равен элементу на котором произошло 40 | // событие, поэтому, нужно исправить код так, чтобы this в обработчиках 41 | // ссылался на реакт компоненту, а не на DOM-ноды. Для этого надо или сделать 42 | // bind для обработчиков или вызвать обработчики внутри стрелочной функции] 43 | // 2. Обработчики onInput и onInvalid нельзя ставить на форму, они должны быть 44 | // на каждом элементе отдельно 45 | // 3. Форма не отрендерится, ей обязательно нужно указать action 46 | // 4. Атрибуты min и step для возраста нужно указывать строкой 47 | -------------------------------------------------------------------------------- /07-event-capture.jsx: -------------------------------------------------------------------------------- 1 | // Вопрос: В этом коде не работает обработка события invalid, как починить? 2 | 3 | class Form extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | this.state = { 7 | errorMessage: '', 8 | }; 9 | } 10 | 11 | render() { 12 | return ; 19 | } 20 | 21 | onInput() { 22 | this.setState({ errorMessage: '' }); 23 | } 24 | 25 | onInvalid(evt) { 26 | evt.preventDefault(); 27 | this.setState({ 28 | errorMessage: evt.target.name === 'fullname' 29 | ? 'Введите имя, это обязательное поле' 30 | : 'Введите возраст. Возраст не должен быть меньше 18 лет' 31 | }); 32 | } 33 | } 34 | 35 | ReactDOM.render(, document.querySelector("#app")); 36 | 37 | // Варианты ответа 38 | // 1. Вместо onInvalid нужно написать onInvalidCapture [правильный ответ] 39 | // [Обработка не работает, потому что у этого события нет фазы всплытия, 40 | // поэтому его нужно ловить на фазе захвата.] 41 | // 2. onInvalid нужно убрать с формы и поставить на инпуты [правильный ответ] 42 | // [Обработка не работает, потому что у этого события нет фазы всплытия, 43 | // поэтому можно поставить обработчики на каждый из элементов формы и забить 44 | // на делегирование. Это будет работать, но это небольшой оверхед] 45 | // 3. Такого события не существует, нужно обрабатывать событие submit 46 | // 4. Правильное название события не onInvalid а onIncorrectInput 47 | -------------------------------------------------------------------------------- /08-event-bind-why.jsx: -------------------------------------------------------------------------------- 1 | // Вопрос: Ок, поставили bind для обработчиков и сделали capture. Как можно оптимизировать создание 2 | // обработчиков? 3 | 4 | class Form extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = { 8 | errorMessage: '', 9 | }; 10 | } 11 | 12 | render() { 13 | return ; 20 | } 21 | 22 | onInput() { 23 | this.setState({ errorMessage: '' }); 24 | } 25 | 26 | onInvalid(evt) { 27 | evt.preventDefault(); 28 | this.setState({ 29 | errorMessage: evt.target.name === 'fullname' 30 | ? 'Введите имя, это обязательное поле' 31 | : 'Введите возраст. Возраст не должен быть меньше 18 лет' 32 | }); 33 | } 34 | } 35 | 36 | ReactDOM.render(, document.querySelector("#app")); 37 | 38 | // Варианты ответа 39 | // 1. Перенести создание .bind-версий обработчиков в конструктор [правильный ответ] 40 | // [В текущей версии кода при каждом вызове render будет создаваться новый 41 | // обработчик, который будет представлять собой функцию, с привязанным контекстом. 42 | // Чтобы избежать создания абсолютно одинаковых функций, при каждом вызове render, 43 | // можно создать функции один раз и они будут переиспользоваться] 44 | // 2. Добавить evt.preventDefault() в onInput() 45 | // 3. Наоборот, убрать evt.preventDefault() из onInvalid() 46 | // 4. Нужно в оба обработчика добавить evt.stopPropagation() 47 | -------------------------------------------------------------------------------- /09.wrong-state-write.jsx: -------------------------------------------------------------------------------- 1 | // Вопрос: Почему эта форма не заработает? 2 | 3 | class Form extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | 7 | this.state = { 8 | errorMessage: [], 9 | }; 10 | } 11 | 12 | render() { 13 | return ; 21 | } 22 | 23 | onInput() { 24 | if (!this.refs.formElementRef['fullname'].value) { 25 | this.state.messages.push('Введите имя, это обязательное поле'); 26 | } 27 | 28 | if (!this.refs.formElementRef['age'] || 29 | this.refs.formElementRef['age'].value < 18) { 30 | this.state.messages.push('Введите возраст. Возраст не должен быть меньше 18 лет'); 31 | } 32 | } 33 | } 34 | 35 | ReactDOM.render(, document.querySelector("#app")); 36 | 37 | // Варианты ответа 38 | // 1. Идет неправильная запись в state в onInput [правильный ответ][state нужно 39 | // обновлять через setState,а не через прямое обращение к свойствам объекта] 40 | // 2. disabled у кнопки не будет переключаться, выражение закешируется 41 | // и не будет обновляться, даже когда изменится length массива, потому что 42 | // объекты передаются по ссылке 43 | // 3. Всё дело в тернарном операторе в render(), так код писать нельзя: нужно 44 | // использовать if 45 | // 4. В onInput неправильная проверка поля возраста 46 | -------------------------------------------------------------------------------- /10.wrong-ref.jsx: -------------------------------------------------------------------------------- 1 | // Вопрос: Почему эта форма не заработает? 2 | 3 | class Form extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | 7 | this.state = { 8 | errorMessage: [], 9 | }; 10 | } 11 | 12 | render() { 13 | return ; 21 | } 22 | 23 | onInput() { 24 | const messages = []; 25 | 26 | if (!this.refs.formElementRef['fullname'].value) { 27 | messages.push('Введите имя, это обязательное поле'); 28 | } 29 | 30 | if (!this.refs.formElementRef['age'] || 31 | this.refs.formElementRef['age'].value < 18) { 32 | messages.push('Введите возраст. Возраст не должен быть меньше 18 лет'); 33 | } 34 | 35 | this.setState({ 36 | errorMessage: messages, 37 | }); 38 | } 39 | } 40 | 41 | ReactDOM.render(, document.querySelector("#app")); 42 | 43 | // Варианты ответа 44 | // 1. Всё нормально [правильный ответ][сложность не в использовании рефа, 45 | // а в обращении к элементу формы по name через нотацию с квадратными 46 | // скобками] 47 | // 2. При обращении к рефу нужно использовать `current`, 48 | // не `this.formElementRef['age']`, a `this.formElementRef.current['age']` 49 | // [Необязательно, тут создаётся реф как просто строка] 50 | // 3. Чтобы заработало нужно создать два отдельных рефа для каждого из инпутов 51 | // 4. disabled у кнопки не будет переключаться, выражение закешируется 52 | // и не будет обновляться, даже когда изменится length массива, потому что 53 | // объекты передаются по ссылке 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Задачи на собеседование на понимание React и JS в целом. В части задач всё написано правильно, но код не работает из-за синтаксических ошибок или мелких косяков из-за непонимания синтаксиса JS. В части задач всё работает и нужно это опознать. Этому может мешать, например, непривычное использование каких-нибудь API. 2 | 3 | Work in progress. 4 | -------------------------------------------------------------------------------- /templates/01.button-count.jsx: -------------------------------------------------------------------------------- 1 | // Базовая разметка: кнопка считает количество нажатий 2 | class Button extends React.Component { 3 | constructor(props) { 4 | super(props); 5 | this.state = { 6 | clicked: 0 7 | }; 8 | } 9 | 10 | render() { 11 | return ; 12 | } 13 | 14 | onClick() { 15 | this.setState({ 16 | clicked: this.state.clicked + 1 17 | }); 18 | } 19 | } 20 | 21 | ReactDOM.render(, document.querySelector("#app")) 22 | -------------------------------------------------------------------------------- /templates/02.button-count-redhot.jsx: -------------------------------------------------------------------------------- 1 | // Сложная работа со стейтом: кнопка считает количество нажатий и краснеет, 2 | // если их было больше 10 3 | class Button extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | this.state = { 7 | clicked: 0, 8 | redHot: false, 9 | }; 10 | } 11 | 12 | render() { 13 | return ; 16 | } 17 | 18 | onClick() { 19 | this.setState({ 20 | clicked: this.state.clicked + 1 21 | }, () => { 22 | this.setState({ 23 | redHot: this.state.clicked >= 10 24 | }); 25 | }); 26 | } 27 | } 28 | 29 | ReactDOM.render(, document.querySelector("#app")); 30 | -------------------------------------------------------------------------------- /templates/03.form-dom-validity.jsx: -------------------------------------------------------------------------------- 1 | class Form extends React.Component { 2 | constructor(props) { 3 | super(props); 4 | this.state = { 5 | errorMessage: '', 6 | }; 7 | } 8 | 9 | render() { 10 | return ; 17 | } 18 | 19 | onInput() { 20 | this.setState({ errorMessage: '' }); 21 | } 22 | 23 | onInvalid(evt) { 24 | evt.preventDefault(); 25 | this.setState({ 26 | errorMessage: evt.target.name === 'fullname' 27 | ? 'Введите имя, это обязательное поле' 28 | : 'Введите возраст. Возраст не должен быть меньше 18 лет' 29 | }); 30 | } 31 | } 32 | 33 | ReactDOM.render(, document.querySelector("#app")); 34 | -------------------------------------------------------------------------------- /templates/04.form-validity.jsx: -------------------------------------------------------------------------------- 1 | class Form extends React.Component { 2 | constructor(props) { 3 | super(props); 4 | 5 | this.state = { 6 | errorMessage: [], 7 | }; 8 | } 9 | 10 | render() { 11 | return ; 19 | } 20 | 21 | onInput() { 22 | const messages = []; 23 | 24 | if (!this.refs.formElementRef['fullname'].value) { 25 | messages.push('Введите имя, это обязательное поле'); 26 | } 27 | 28 | if (!this.refs.formElementRef['age'] || 29 | this.refs.formElementRef['age'].value < 18) { 30 | messages.push('Введите возраст. Возраст не должен быть меньше 18 лет'); 31 | } 32 | 33 | this.setState({ 34 | errorMessage: messages, 35 | }); 36 | } 37 | } 38 | 39 | ReactDOM.render(, document.querySelector("#app")); 40 | -------------------------------------------------------------------------------- /templates/README.md: -------------------------------------------------------------------------------- 1 | Шаблонные файлы с правильным кодом компонент для разных задач. Есть шаблоны разных по сложности компонент 2 | 3 | - `01.button-count.jsx`: кнопка, считающая количество нажатий 4 | 5 | - `02.button-count-redhot.jsx`: кнопка, считающая количество нажатий и изменяющая цвет после достижения определённого количества 6 | 7 | - `03.form-dom-validity.jsx`: форма, валидирующаяся через DOM API. Элементам поставлены соответствующие атрибуты и при наступлении события invalid (попытка отправить форму), в специальный блок выводится сообщение об ошибке в соответствующем поле 8 | 9 | - `04.form-validity.jsx`: кастомная проверка валидности формы: при любом изменении поля, проверяется валидность всех полей формы и список ещё невалидных полей выводится над кнопкой отправки. Кнопка заблокирована, пока форма не будет отправлена 10 | --------------------------------------------------------------------------------