ZarahioN Presents

Answering why

[Pt. 6.42 — Scary hooks]

Крепко задумавшись в прошлый раз о состоянии я вспомнил что забыл о одном очень важном элементе буквальной жизнедеятельности компонентов реакта — lifecycle hooks.

Думаю, если ты знаком с WordPress-ом (или другими представителями систем расширяемых «событийными реакциями») то примерно знаешь или хотя бы слышал такой термин как hook. По сути крючок (hook) — это крючок, как ни странно. Он позволяет «зацепиться» за какую-то часть программы и построить свой небольшой замок в песочнице, или добавить башенку замку большого и доброго старшего товарища.
Если ты немного не понял мою креативную аллегорию — крючки позволяют добавлять свое или изменять «чужое» поведение в программе.

Продолжая аналогию с WordPress, который предоставляет огромное количество таких крючков, можно добавить код выполняющийся в конце записи, к примеру, который будет показывать следующую и предыдущую запись из набора или всего списка, как сделано (скорее всего) у нас.

Впрочем, возвращаясь к реакту, как бы страшно их не называли, крючки это большей частью логические события: компонент готов к работе — событие, компонент собирается покинуть нас — событие, компонент получил новые свойства — ? тоже событие. Такая трактовка немножко может не соответствовать технической действительности, однако это не мешает ей быть достаточно точной для понимания и использования.

Как можно заметить, я назвал пару примерных и на удивление правдоподобно звучащих крючка, помимо них есть еще парочка, с полным списком и официальной трактовкой которых можно ознакомиться здесь, но часто используемые мы разберем сейчас.

Однако для начала — что (технически) есмь хук?

В реалиях реакта крючок — это метод класса, из чего ненавязчиво вытекает что они доступны только полноценным компонентам. Как правило, в реакте крючки используются для того, чтобы выполнить какую-то внешнюю активность в подходящий момент: повесить обработчик на событие вне дерева реакта (всеми любимый window), начать загрузку данных, убрать повешенный ранее обработчик и подобные вариации.

Соответственно для таких действий есть два, пожалуй, чаще всего используемых хука: componentDidMount и componentWillUnmount, первый вызывается как только компонент завершил монтирование (рендер) на свое место в дереве реакта и реального DOM, второй — в момент перед размонтированием — отключением из дерева компонентов и реального DOM.

Классический пример использования первого — загрузка начальных данных для компонента, или даже приложения, если совершать ее достаточно высоко в дереве:

class DemoLoader extends React.Component {
    state = {
        rows: []
    }
    componentDidMount() {
        fetch(apiUrl)
        .then(resp => resp.ok ? resp.json() : new Error(resp))
        .then(data => this.setState({ rows: data }))
        .catch(err => console.warn(err));
    }
    render() {
        return (
            <table>
                {this.state.rows.map(row => <tr><td>{row.text}</td></tr>)}
            </table>
        );
    }
}

Как можно заметить, я инициализирую состояние с одной пустой переменной rows, которая в последующем заполняется через fetch при монтировании компонента. «Реальную» демонстрацию можно посмотреть на codepen.

Что же происходит?

Реакт, или точнее его компоненты, имеют так называемый жизненный цикл (life cycle) — набор действий или состояний, через который проходит любой используемый компонент. Он начинается в constructor()-е компонента и заканчивается при размнотировании componentWillUnmount(), проходя между ними «в районе» десятка шагов которые нам доступны, пара из которых нам бывает полезна очень часто, componentDidMount() — один из таких шагов.

Какие они есть?

Во первых полезные края — componentDidMount и componentWillUnmount.
Они нужны в первую очередь для доступа к реальному DOM изнутри реакта. Во вторую очередь componentDidMount используется для действий, которые нужно совершить как только компонент «готов к работе» — отрендерен и смонтирован. Как, к примеру, загрузка данных в моем недавнем скромном примере.

Я несколько раз уже повторил термин монтирования по ходу разбора реакта, в подробностях я расскажу о нем позже, но вкратце — реакт использует виртуальный легкий DOM в котором оперирует а после исполнения изменений сравнивает свой виртуальный и браузерный реальный DOM совершая необходимые изменения в последнем, таким образом смонтированные компоненты — это компоненты, которые уже присутствуют в реальном DOM.

Как ни странно, но следующим идет непосредственно render(), в частности повторяющийся при каждом изменении компонента. Это, как уже думаю понятно, освной метод любого компонента — именно он задает Представление.

Последним частенько используемым и очень важным крючком является метод shouldComponentUpdate(). В отличии от уже разобранных, которые так или иначе влияли непосредственно на компонент или приложение в целом, shouldComponentUpdate является чисто контролирующим — как можно понять из названия, с его помощью можно указать стоит ли компоненту обновляться.

Это нужно в случаях, когда у нас есть, к примеру, таблица в пару тысяч строк — по умолчанию если у нас измениться хоть одно свойство у родительского компонента (table) то реакт будет вынужден заново провести рендер каждого вложенного ряда (tr) — что естественно не имеет смысла в большинстве случаев. Поэтому мы можем сами решить, стоит ли ререндериться компоненту — как правило сравнивая переданные в shouldComponentUpdate(newProps) новый набор свойств с текущим набором в компоненте this.props.

К слову, над совсем глупыми случаями ререндера команда реакта работает и за последний год было пару новвоведений вроде React.PureComponent и просто улучшений ситуации, однако всегда есть вероятность, что тебе придется самому заняться оптимизацией, и ненужно обновляющиеся компоненты — первый подозреваемый.
(Для поиска и отладки таких компонентов можно использовать официальное расширение для реакта или неофициальный инструмент по типу whyDidYouUpdate?!)

Отдельно стоит исключительный метод componentDidCatch, для поимки исключений.
I’ll exit myself out..

Он получает ошибку и дополнительную информацию в качестве аргументов и может быть использован для перенаправления рендера на показ сообщения о ошибке (или чего угодно). При этом он ловит все необработанные исключения ниже своего компонента в дереве реакта — он не словит обработанное, к примеру другим компонентом реализующим данный метод, исключение и он не словит исключение в произошедшее в самом себе.

Оставшиеся пару крючков можешь посмотреть в официальной документации, но я сомневаюсь что они понадобятся тебе в ближайшем будущем — они больше направлены на контроль и изменение свойств во время работы компонента, что как правило нужно только при создании библиотек либо вмешательстве в чужие компоненты.

На сим я завершаю наше короткое ознакомление с жизненным циклом компонентов реакта.

Leave a Reply

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