Хабрахабр

Разбор конкурса-квиза по React со стенда HeadHunter на HolyJs 2018

24–25 сентября в Москве прошла конференция фронтенд-разработчиков HolyJs https://holyjs-moscow.ru/. Привет. Был основной квиз — 4 отборочных тура и 1 финальный, на котором были разыграны Apple Watch и конструкторы лего. Мы на конференцию пришли со своим стендом, на котором проводили quiz. И отдельно мы провели квиз на знание react.

Правильные варианты будут спрятаны под спойлером, поэтому вы можете не только почитать разбор, но и проверить себя 🙂 Под катом — разбор задач квиза по react.

image

Поехали!

Для удобства мы сгруппировали вопросы по секциям:

Секция 1. Базовое понимание работы this.setState и updating lifecycle компонента:

Вопрос 1.

Выберите наиболее полный список способов обновить react-компонент: 1) SetProps, SetState, ForceUpdate
2) ForceUpdate, SetState
3) ForceUpdate, SetState, Parent (re)render
4) ForceUpdate, SetState, directly call UpdateComponent

Ответ

3) ForceUpdate, SetState, Parent (re)render

Вопрос 2.

Что произойдет, если вызвать this.setState() в react 1) Компонент пометится грязным, вызовется updating lifecycle
2) Ничего не произойдет, компонент не обновится
3) React упадет с ошибкой "Object cannot be empty"
4) Все поля в state будут заресечены

Ответ

1) Компонент пометится грязным, вызовется updating lifecycle

Разбор вопросов 1 и 2

Для ответа на вопрос разберем 2 части:
1) Собственный запрос компонента на updating цикл
2) Запрос снаружи компонента

В этом случае компонент будет помечен грязным и на тик Reconcilliation, если он будет в приоритете на рендеринг, запустится updating цикл. У самого компонента есть 2 способа обновить самого себя:
1) this.setState и this.forceUpdate.

При вызове this.setState({}) вызывается полный updating цикл, в отличие от this.forceUpdate, когда updating цикл запускается без shouldComponentUpdate метода. Интересный факт: this.setState({}) и this.forceUpdate отличаются. Пример работы this.setState({}) можно посмотреть здесь: https://codesandbox.io/s/m5jz2701l9 (если заменить в примере setState на forceUpdate, можно посмотреть, как изменится поведение компонентов).

Полного пересчета поддерева можно избежать, описав shouldComponentUpdate или определив компонент как PureComponent. 2) Когда родитель компонента ререндерится, он возвращает часть vDOM, все children, которые должны будут обновиться, — и у них также будет вызван полный updating lifecycle.

Вопрос 3

Чем отличается Component от PureComponent (PC) 1) Component не поддерживает наследование, в отличие от Pure
2) PC реализует SCU, проводит shallowEqual props и state
3) PC используют только для компонентов, которые зависят от store
4) В PC необходимо определять функцию shouldComponentUpdate

Ответ и разбор

2) PC реализует SCU, проводит shallowEqual props и state

Представьте, что у вас обновился корневой элемент. Как мы обсудили ранее, при (ре)рендеринге родителя все поддерево будет отправлено на updating lifeCycle. Чтобы оптимизировать и не отправлять лишнее на updating, в react есть метод shouldComponentUpdate, который позволяет вернуть true, если компонент должен обновиться, и false в ином случае. В этом случае по цепному эффекту у вас должно будет обновиться практически все react-дерево. Для упрощения сравнения в react, можно унаследоваться от PureComponent, чтобы получить сразу готовый shouldComponentUpdate, который сравнит по ссылке (если речь идет об object types) или по значению (если речь про value types) все props и state, которые приходят в компонент.

Вопрос 4.

this.setState(() => {}, () => {}) — зачем нужно передавать вторую функцию в setState? 1) set принимает набор объектов. Они смержатся перед updating
2) Вторая функция будет вызвана после обновление state
3) setState принимает только 1 аргумент

Ответ и разбор

2) Вторая функция будет вызвана после обновление state

Например, сделать http-запрос, внести какие-то стилевые изменения, получить метрики html-элементов и (по условию) сделать setState. В React-lifecycle есть два метода: componentDidMount для mounting цикла и componentDidUpdate для updating, где можно добавить какую-то логику после обновления компонента. Если же вы хотите сделать какое-то действие после изменения определенных полей в state, то в методе componentDidUpdate придется писать либо сравнение:

componentDidUpdate(prevProp, prevState) { if (prevState.foo !== this.state.foo) { // do awesome things here }
}

Либо вы можете сделать это по setState:

setState( // set new foo {foo: 'baz'}, () => { // do awesome things here }
);

У каждого подхода есть плюсы и минусы (например, если вы изменяете setState в нескольких местах, может оказаться удобнее написать один раз условие).

Вопрос 5.

Сколько раз будет выведено в консоль render: class A extends React.PureComponent { render() { console.log('render'); return <div /> }
}
function Test() { return <A foo='bar' onClick={() => console.log('foo')} />
} const rootElement = document.getElementById("root");
ReactDOM.render(<Test />, rootElement);
setTimeout(() => ReactDOM.render(<Test />, rootElement)); 1) 1
2) 2
3) 3
4) 0

Ответ

Вопрос 6.

Сколько раз будет выведено в консоль render: class A extends React.PureComponent { render() { console.log('render'); return <div /> }
}
function Test() { return <A foo='bar' />
} const rootElement = document.getElementById("root");
ReactDOM.render(<Test />, rootElement);
setTimeout(() => ReactDOM.render(<Test />, rootElement)); 1) 1
2) 2 3) 3
4) 0

Ответ

Вопрос 7.

Сколько раз будет выведено в консоль render: class A extends React.PureComponent { componentDidMount() { console.log('render'); } render() { return <div /> }
} const rootElement = document.getElementById("root");
ReactDOM.render(<A />, rootElement);
setTimeout(() => ReactDOM.render(<A />, rootElement)); 1) 1
2) 2 3) 3
4) 0

Ответ

Разбор вопросов 5-7

Если внутри метода render мы передаем в виде jsx колбек, описывая это прямо в функции render: Вопросы 5–7 Нужны для одного и того же — проверить понимание работы PureComponent и обновления компонентов при передаче props.

render () { return <Button onClick={() => {}} />;
}

Это происходит, потому что при каждом рендере создается новая функция с уникальной ссылкой, которая при сравнении в PureComponent выдаст, что новые props не равны старым и нужно обновить компонент. То каждый render родителя будет обновлять данный хендлер клика. В случае же, когда все проверки проходят и shouldComponentUpdate возвращает false, обновления не происходит.

Секция 2. Keys in React

Подробный разбор работы keys мы публиковали здесь: https://habr.com/company/hh/blog/352150/

Вопрос 1.

Для чего может потребоваться key, если работа происходит не с массивом? 1) Удалить предыдущий инстанс и замаунтить новый при смене key
2) Дополнительный способ вызвать updating lifecycle
3) Причин использовать key нет
4) Для форсирования механизма reconciliation

Ответ и разбор

1) Удалить предыдущий инстанс и замаунтить новый при смене key

Если мы используем key, сравнение будет происходить по соответствующим key. Без использования key react будет сравнивать список элементов попарно сверху вниз. Если появился новый key — то такой компонент не будет сравниваться ни с кем и сразу будет создан с нуля.
Этим способом можно пользоваться, даже если у нас есть 1 элемент: мы можем задать <A key="1" />, в следующем рендере укажем <A key="2" /> и в таком случае react удалит <A key="1" /> и создаст с нуля <A key="2" />.

Вопрос 2.

Имеет ли сам компонент доступ к this.prop.key? 1) Да
2) Нет
3) Необходимо определить static getKey

Ответ и разбор

2) Нет

Компонент может узнать key у своих children, которые были переданы ему в качестве prop, но не может узнать о своем key.

Вопрос 3.

Сколько раз будет выведено в консоль render: class A extends React.PureComponent { componentDidMount() { console.log('render'); } render() { return <div /> }
} const rootElement = document.getElementById("root");
ReactDOM.render(<A key='1' />, rootElement);
setTimeout(() => ReactDOM.render(<A />, rootElement)); 1) 1 2) 2
3) 3
4) 0

Ответ и разбор

2) 2

При изменении key компонент будет пересоздан, поэтому render будет выведен дважды.

Секция 3. Вопросы по jsx

Вопрос 1.

Выберите подходящий ответ. Дочерний компонент может уведомить своего родителя об изменениях с помощью 1) Колбека в виде prop / context
2) Выноса слоя модели и работы через нее
3) Определения setParentProps
4) Через static getParentRef

Ответ и разбор

1) Колбека в виде prop / context
2) Выноса слоя модели и работы через нее

Выбор любого из них на квизе засчитает вам баллы. Здесь есть два правильных ответа. Данные сверху вниз распространяются в виде props или context, в них может быть callback, который компонент ниже может вызывать, чтобы повлиять на состояние системы.
Другой способ, сочетающий вынос модели, context и prop, — это, например, react-redux биндинг.
Эта библиотека берет вынесенную из react модель (redux). Данный вопрос на знания data-flow react. Затем разработчик использует HOC connect, который идет в контекст, подписывается на изменения store (store.subscribe) и при изменении store пересчитывает mapStateToProps функцию. Сетит redux.store в Provider, который на самом деле сетит store в context. Их, в свою очередь, мы получаем извне (без контекста), биндим actionCreators на store (оборачиваем их в store.dispatch) и передаем в качестве props оборачиваемому компоненту. Если данные изменились, сетит их в props в оборачиваемый объект.
В то же время connect позволяет указать mapDispatchToProps, где разработчик указывает те actionCreators, которые необходимо передать в компонент.

Вопрос 2.

В какие props можно передавать jsx? Выберите наиболее подходящий ответ 1) В любые
2) Только в children

Ответ и разбор

1) В любые

Например: Передавать можно в любые.

<Button icon={<Icon kind='warning'/>}>Внимание</Button>

Такой подход очень удобно использовать, чтобы оставлять за компонентом право управлять расположением различных элементов относительно друг друга, а не перебирать один children prop. Нарисует кнопку с иконкой.

Секция 4. Продвинутое понимание setState

Здесь 3 сильно связанных вопроса:

Вопрос 1.

this.state = {a: 'a'}; ...
this.setState({a: 'b'});
this.setState({a: this.state.a + 1}) this.state? 1) {a: 'a1'}
2) {a: 'b1'}
3) Недостаточно данных
4) {a: 'a'}

Ответ

3) Недостаточно данных

Вопрос 2.

this.state={a: 'a'} ...
this.setState({a: 'b'}) this.setState(state => {a: state.a + 1}) this.state? 1) {a: 'a1'}
2) {a: 'b1'}
3) Недостаточно данных
4) {a: 'ab1'}

Ответ

2) {a: 'b1'}

Вопрос 3.

При вызове подряд 2 setState внутри componentDidUpdate сколько updating lifecycle будет вызвано 1) 1
2) 2
3) 3
4) Недостаточно данных

Ответ

Разбор вопросов 1–3

Вся работа setState полностью описана здесь:
1) https://reactjs.org/docs/react-component.html#setstate
2) https://stackoverflow.com/questions/48563650/does-react-keep-the-order-for-state-updates/48610973#48610973

Они накатываются подряд друг за другом, поэтому в случае, если мы находимся внутри react-handler, мы получим: Дело в том, что setState не происходит синхронно.
И в случае, если есть несколько вызовов setState подряд, то в зависимости от того, находимся ли мы внутри react-lifecycle метода, функции-обработчика react-события (onChange, onClick) или нет, зависит исполнение setState.
Внутри react обработчиков setState работает батчево (изменения накатываются только после того, как пользовательские функции в call stack закончатся и мы попадем в функции, которые вызывали наши event handler и lifecycle методы).

this.state = {a: 'a'}; // a: 'a'
...
this.state.a // a: 'a'
this.setState({a: 'b'}); // a: 'b' + компонент не обновляется. Была зарегистрирована только необходимость в этом
this.state.a // a: 'a'
this.setState({a: this.state.a + 1}) // a: 'a1'

так как изменения произошли батчево.
Но в тоже время, если setState был вызван вне react-handlers:

this.state = {a: 'a'}; // a: 'a'
...
this.state.a // a: 'a'
this.setState({a: 'b'}); // a: 'b' + компонент ушел на ререндер
this.state.a // a: 'b'
this.setState({a: this.state.a + 1}) // a: 'b1' + компонент ушел на ререндер

Так как в этом случае изменения будут накатываться отдельно.

Секция 5. Redux

Вопрос 1.

Можно ли задавать кастомные action, например () => {} ? 1) Нет. Все action должны быть объектом с полем type
2) Да, но такой action должен вернуть объект с полем type
3) Да, нужно определить кастомный middleware для такого action
4) Да, но такая функция должна принимать метод dispatch

Ответ и разбор

3) Да, нужно определить кастомный middleware для такого action

Весь middleware — это небольшой блок кода:
https://github.com/reduxjs/redux-thunk/blob/master/src/index.js#L2-L9 Возьмем в качестве простейшего примера redux-thunk.

return ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } return next(action);
};

Поэтому action, который был задиспачен, вначале пройдет по цепочке middleware.
Каждый middleware принимает инстанс store, метод next, который позволяет пробросить action далее, и cам action.
Если middleware обрабатывает кастомные action, как, например, redux-thunk, то он в случае, если action является функцией, не пробрасывает action далее, а "заглушает" его, вместо этого вызывая action с передачей туда метода dispatch и getState.
Что бы случилось, если redux-thunk сделал next для action, который является функцией?
Перед вызовом редьюсеров store проверяет тип action. Как работают middleware?
Они получают управление до того, как action придет в store. Он должен удовлетворять следующим условиям:
1) Это должен быть объект
2) У него должно быть поле type
3) Поле type должно быть типа string

Если одно из условий не выполняется, redux выдаст ошибку.

Бонусные вопросы:

Бонусный вопрос 1.

Что будет выведено? class Todos extends React.Component { getSnapshotBeforeUpdate(prevProps, prevState) { return this.props.list.length - prevProps.list.length; } componentDidUpdate(a, b, c) { console.log(c); } ...
} ReactDOM.render(<Todos list={['a','b']} />, app);
setTimeout(() => ReactDOM.render(<Todos list={['a','b','a','b']} />, app), 0); a) 0
b) 1
c) 2
d) undefined

Ответ и разбор

c) 2

Этот метод нужен, чтобы заранее подсчитать те или иные данные, на основе которых можно затем сделать, например, fetch-запрос. getSnapshotBeforeUpdate — редко используемая функция в react, которая позволяет получить снепшот, который затем будет передан в componentDidUpdate.

Бонусный вопрос 2.

Чему будет равно значение в инпуте через 2,5 секунды? function Input() { const [text, setText] = useState("World!"); useEffect( () => { let id = setTimeout(() => { setText("Hello " + text); }, 1000); return () => { clearTimeout(id); }; }, [text] ); return ( <input value={text} onChange={e => { setText(e.target.value); }} /> );
} a) "World!"
b) "Hello World!" c) "Hello Hello World!"
d) В коде ошибка

Ответ

c) "Hello Hello World!"

Давайте попробуем в комментариях подробно описать работу кода из последнего вопроса 🙂 Это уже вопрос на знание новых фичей в react, его не было в нашем квизе.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть