Разбор конкурса-квиза по React со стенда HeadHunter на HolyJs 2018
- пятница, 30 ноября 2018 г. в 00:19:07
Привет. 24–25 сентября в Москве прошла конференция фронтенд-разработчиков HolyJs https://holyjs-moscow.ru/. Мы на конференцию пришли со своим стендом, на котором проводили quiz. Был основной квиз — 4 отборочных тура и 1 финальный, на котором были разыграны Apple Watch и конструкторы лего. И отдельно мы провели квиз на знание react.
Под катом — разбор задач квиза по react. Правильные варианты будут спрятаны под спойлером, поэтому вы можете не только почитать разбор, но и проверить себя :)
Поехали!
Для удобства мы сгруппировали вопросы по секциям:
Вопрос 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
Для ответа на вопрос разберем 2 части:
1) Собственный запрос компонента на updating цикл
2) Запрос снаружи компонента
У самого компонента есть 2 способа обновить самого себя:
1) this.setState и this.forceUpdate. В этом случае компонент будет помечен грязным и на тик Reconcilliation, если он будет в приоритете на рендеринг, запустится updating цикл.
Интересный факт: this.setState({})
и this.forceUpdate
отличаются. При вызове this.setState({})
вызывается полный updating цикл, в отличие от this.forceUpdate
, когда updating цикл запускается без shouldComponentUpdate метода. Пример работы this.setState({})
можно посмотреть здесь: https://codesandbox.io/s/m5jz2701l9 (если заменить в примере setState на forceUpdate, можно посмотреть, как изменится поведение компонентов).
2) Когда родитель компонента ререндерится, он возвращает часть vDOM, все children, которые должны будут обновиться, — и у них также будет вызван полный updating lifecycle. Полного пересчета поддерева можно избежать, описав shouldComponentUpdate или определив компонент как PureComponent.
Вопрос 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. Представьте, что у вас обновился корневой элемент. В этом случае по цепному эффекту у вас должно будет обновиться практически все react-дерево. Чтобы оптимизировать и не отправлять лишнее на updating, в react есть метод shouldComponentUpdate
, который позволяет вернуть true, если компонент должен обновиться, и false в ином случае. Для упрощения сравнения в react, можно унаследоваться от PureComponent
, чтобы получить сразу готовый shouldComponentUpdate
, который сравнит по ссылке (если речь идет об object types) или по значению (если речь про value types) все props и state, которые приходят в компонент.
Вопрос 4.
this.setState(() => {}, () => {}) — зачем нужно передавать вторую функцию в setState?
1) set принимает набор объектов. Они смержатся перед updating
2) Вторая функция будет вызвана после обновление state
3) setState принимает только 1 аргумент
2) Вторая функция будет вызвана после обновление state
В React-lifecycle есть два метода: componentDidMount
для mounting цикла и componentDidUpdate для updating, где можно добавить какую-то логику после обновления компонента. Например, сделать http-запрос, внести какие-то стилевые изменения, получить метрики html-элементов и (по условию) сделать setState. Если же вы хотите сделать какое-то действие после изменения определенных полей в 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
2) 2
Вопрос 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
1) 1
Вопрос 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
1) 1
Вопросы 5–7 Нужны для одного и того же — проверить понимание работы PureComponent
и обновления компонентов при передаче props. Если внутри метода render мы передаем в виде jsx колбек, описывая это прямо в функции render:
render () {
return <Button onClick={() => {}} />;
}
То каждый render родителя будет обновлять данный хендлер клика. Это происходит, потому что при каждом рендере создается новая функция с уникальной ссылкой, которая при сравнении в PureComponent выдаст, что новые props не равны старым и нужно обновить компонент. В случае же, когда все проверки проходят и shouldComponentUpdate возвращает false, обновления не происходит.
Подробный разбор работы keys мы публиковали здесь: https://habr.com/company/hh/blog/352150/
Вопрос 1.
Для чего может потребоваться key, если работа происходит не с массивом?
1) Удалить предыдущий инстанс и замаунтить новый при смене key
2) Дополнительный способ вызвать updating lifecycle
3) Причин использовать key нет
4) Для форсирования механизма reconciliation
1) Удалить предыдущий инстанс и замаунтить новый при смене key
Без использования key react будет сравнивать список элементов попарно сверху вниз. Если мы используем key, сравнение будет происходить по соответствующим key. Если появился новый 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 будет выведен дважды.
Вопрос 1.
Выберите подходящий ответ. Дочерний компонент может уведомить своего родителя об изменениях с помощью
1) Колбека в виде prop / context
2) Выноса слоя модели и работы через нее
3) Определения setParentProps
4) Через static getParentRef
1) Колбека в виде prop / context
2) Выноса слоя модели и работы через нее
Здесь есть два правильных ответа. Выбор любого из них на квизе засчитает вам баллы. Данный вопрос на знания data-flow react. Данные сверху вниз распространяются в виде props или context, в них может быть callback, который компонент ниже может вызывать, чтобы повлиять на состояние системы.
Другой способ, сочетающий вынос модели, context и prop, — это, например, react-redux биндинг.
Эта библиотека берет вынесенную из react модель (redux). Сетит redux.store в Provider, который на самом деле сетит store в context. Затем разработчик использует HOC connect, который идет в контекст, подписывается на изменения store (store.subscribe) и при изменении store пересчитывает mapStateToProps
функцию. Если данные изменились, сетит их в props в оборачиваемый объект.
В то же время connect позволяет указать mapDispatchToProps
, где разработчик указывает те actionCreators, которые необходимо передать в компонент. Их, в свою очередь, мы получаем извне (без контекста), биндим actionCreators
на store (оборачиваем их в store.dispatch) и передаем в качестве props оборачиваемому компоненту.
Вопрос 2.
В какие props можно передавать jsx? Выберите наиболее подходящий ответ
1) В любые
2) Только в children
1) В любые
Передавать можно в любые. Например:
<Button icon={<Icon kind='warning'/>}>Внимание</Button>
Нарисует кнопку с иконкой. Такой подход очень удобно использовать, чтобы оставлять за компонентом право управлять расположением различных элементов относительно друг друга, а не перебирать один children prop.
Здесь 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) 1
Вся работа 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
Дело в том, что setState не происходит синхронно.
И в случае, если есть несколько вызовов setState подряд, то в зависимости от того, находимся ли мы внутри react-lifecycle метода, функции-обработчика react-события (onChange, onClick) или нет, зависит исполнение setState.
Внутри react обработчиков setState работает батчево (изменения накатываются только после того, как пользовательские функции в call stack закончатся и мы попадем в функции, которые вызывали наши event handler и lifecycle методы). Они накатываются подряд друг за другом, поэтому в случае, если мы находимся внутри react-handler, мы получим:
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' + компонент ушел на ререндер
Так как в этом случае изменения будут накатываться отдельно.
Вопрос 1.
Можно ли задавать кастомные action, например () => {} ?
1) Нет. Все action должны быть объектом с полем type
2) Да, но такой action должен вернуть объект с полем type
3) Да, нужно определить кастомный middleware для такого action
4) Да, но такая функция должна принимать метод dispatch
3) Да, нужно определить кастомный middleware для такого action
Возьмем в качестве простейшего примера redux-thunk. Весь middleware — это небольшой блок кода:
https://github.com/reduxjs/redux-thunk/blob/master/src/index.js#L2-L9
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
Как работают middleware?
Они получают управление до того, как action придет в store. Поэтому action, который был задиспачен, вначале пройдет по цепочке middleware.
Каждый middleware принимает инстанс store, метод next, который позволяет пробросить action далее, и cам action.
Если middleware обрабатывает кастомные action, как, например, redux-thunk, то он в случае, если action является функцией, не пробрасывает action далее, а "заглушает" его, вместо этого вызывая action с передачей туда метода dispatch и getState.
Что бы случилось, если redux-thunk сделал next для action, который является функцией?
Перед вызовом редьюсеров store проверяет тип action. Он должен удовлетворять следующим условиям:
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
getSnapshotBeforeUpdate
— редко используемая функция в react, которая позволяет получить снепшот, который затем будет передан в componentDidUpdate. Этот метод нужен, чтобы заранее подсчитать те или иные данные, на основе которых можно затем сделать, например, fetch-запрос.
Бонусный вопрос 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, его не было в нашем квизе. Давайте попробуем в комментариях подробно описать работу кода из последнего вопроса :)