Review of the React quiz competition from the HeadHunter booth at HolyJs 2018

Published on November 29, 2018

Review of the React quiz competition from the HeadHunter booth at HolyJs 2018

    Hey. September 24–25, HolyJs frontend developers conference https://holyjs-moscow.ru/ took place in Moscow . We came to the conference with our booth where we spent quiz. There was the main quiz - 4 qualifying rounds and 1 final, in which Apple Watch and Lego designers were raffled. And separately, we held a quiz on knowledge.


    Under the cut - analysis of tasks quiz on react. The correct options will be hidden under the spoiler, so you can not only read the analysis, but also check yourself :)


    image


    Go!


    For convenience, we have grouped the questions into sections:


    Section 1. Basic understanding of how this.setState works and when updating the component lifecycle:


    Question 1.


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

    Answer

    3) ForceUpdate, SetState, Parent (re)render


    Question 2.


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

    Answer

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


    Parsing questions 1 and 2

    Для ответа на вопрос разберем 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.


    Question 3


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

    Answer and analysis

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


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


    Question 4.


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

    Answer and analysis

    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 в нескольких местах, может оказаться удобнее написать один раз условие).


    Question 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
    

    Answer

    2) 2


    Question 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

    Answer

    1) 1


    Question 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

    Answer

    1) 1


    Analysis questions 5-7

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


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

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


    Section 2. Keys in React


    A detailed analysis of the work of the keys we published here: https://habr.com/company/hh/blog/352150/


    Question 1.


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

    Answer and analysis

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


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


    Question 2.


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

    Answer and analysis

    2) Нет


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


    Question 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

    Answer and analysis

    2) 2


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


    Section 3. Questions on jsx


    Question 1.


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

    Answer and analysis

    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 оборачиваемому компоненту.


    Question 2.


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

    Answer and analysis

    1) В любые


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


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

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


    Section 4. Advanced understanding of setState


    Here are 3 strongly related questions:


    Question 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'}

    Answer

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


    Question 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'}

    Answer

    2) {a: 'b1'}


    Question 3.


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

    Answer

    1) 1


    Debriefing 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


    Дело в том, что 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' + компонент ушел на ререндер

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


    Section 5. Redux


    Question 1.


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

    Answer and analysis

    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 выдаст ошибку.


    Bonus questions:


    Bonus Question 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

    Answer and analysis

    c) 2


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


    Bonus Question 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) В коде ошибка

    Answer

    c) "Hello Hello World!"


    This is a question about the knowledge of new features in the react, it was not in our quiz. Let's try in the comments to describe in detail the work of the code from the last question :)