{"componentChunkName":"component---src-templates-post-js","path":"/javascript/useeffect-eto-ne-novyi-componentdidmount/","result":{"pageContext":{"slug":"useeffect-eto-ne-novyi-componentdidmount","content":"\n<p>Они почти одинаковые. Но существует достаточно различий, которые возможно доставят вам неудобства, особенно если вы делаете рефакторинг с классов.</p>\n\n\n\n<p class=\"author\"><a href=\"https://reacttraining.com/blog/useEffect-is-not-the-new-componentDidMount/#summary\"><em>Перевод статьи useEffect(fn, []) is not the new componentDidMount()</em></a></p>\n\n\n\n<p>Мы часто делаем какие-нибудь настройки в компоненте при первом монтировании, например запросы в сеть или начинаем слушать события. Так мы приучили себя думать в границах&nbsp;<em>“моментов времени”</em>, таких как&nbsp;<code>componentDidMount()</code>,&nbsp;<code>componentDidUpdate()</code>&nbsp;и&nbsp;<code>componentWillUnmount()</code>. Вполне естественно брать предыдущие знания за основу и искать их аналоги при реализации на хуках. Я сам так делал и да, думаю, что каждый так делал. Часто я слышу на своих воркшопах такую фразу:</p>\n\n\n\n<blockquote class=\"wp-block-quote\"><p><em>‘Какой эквиалент в хуках у [любой метод жизненного цикла]’</em></p></blockquote>\n\n\n\n<p>Быстрый ответ&nbsp;тут таков, что хуки это сдвиг парадигмы от того, чтобы думать в “<em>жизненных циклах и времени</em>” к ведению рассуждений в&nbsp;<em>“основываясь на стейте и синхронизации в DOM”</em>. Если вы возьмете старую парадигму и примените её к хукам, то она просто не будет работать как надо и вполне может застопорить всю вашу работу.</p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" width=\"230\" height=\"278\" src=\"https://stasonmars.ru/wp/wp-content/uploads/2021/01/1_5AGbGZDRF6BjQx0jJn41wQ.gif\" alt=\"\" class=\"wp-image-86\"/></figure>\n\n\n\n<p id=\"d959\">Но эта тема полна свойственной React терминологии и без глубокого и осязаемого объяснения она просто забудется.</p>\n\n\n\n<p id=\"2865\">Когда разработчики только начинают изучать хуки, до этого имея опыт работы только на классах, то они склонны думать, что мол&nbsp;<em>“ага мне нужно запустить какой-либо код единожды при монтировании как с&nbsp;</em><code><em>componentDidMount()</em></code><em>. Ааа, я вижу, что&nbsp;</em><code><em>useEffect</em></code><em>&nbsp;имеет пустой массив как зависимость. Окей, я знаю как это работает.”</em></p>\n\n\n\n<p id=\"5e3d\">Такой ход мыслей приведет нас к нескольким проблемам:</p>\n\n\n\n<p id=\"978a\">Они просто по сути исполнения совершенно разные, так что вы можете не получить желаемого эффекта, рассматривая их как эквиваленты друг другу.</p>\n\n\n\n<p id=\"f452\">Если вы будете рассуждать в стиле:&nbsp;<em>“ага, сейчас вызову сайд эффект разок при монтировании”</em>, то к сожалению это значительно притормозит ваш процесс захода в хуки.</p>\n\n\n\n<p id=\"c024\">Поймите, что рефакторинг из классов на хуки не будет означать банальной замены&nbsp;<code>componentDidMount</code>&nbsp;на&nbsp;<code>useEffect(fn, [])</code>.</p>\n\n\n\n<h2 id=\"da9b\"><strong>Они запускаются в разные временные отрезки</strong></h2>\n\n\n\n<p id=\"67b2\">Сначала давайте поговорим о моменте времени выполнения каждого из них.&nbsp;<code>componentDidMount</code>&nbsp;запускается после монтирования компонента. По документации, если вы сразу же выставляете стэйт(синхронно), то React уже знает как инициировать дополнительный рендер и использовать второй респонс как изначальный UI, чтобы пользователь не увидел мигание между ререндерингом компонентов. Представьте, что вам надо узнать ширину DOM элемента с&nbsp;<code>componentDidMount</code>&nbsp;и вам надо обновить стэйт, чтобы что-нибудь отработало в зависимости от ширины взятого элемента. Вот последовательность действий:</p>\n\n\n\n<p id=\"9714\">Компонент рендерится впервые</p>\n\n\n\n<p id=\"c39e\">Отдаваемое значение&nbsp;<code>render()</code>&nbsp;используется для монтирования нового DOM.</p>\n\n\n\n<p id=\"a8a5\"><code>componentDidMount</code>&nbsp;срабатывает и сразу же выставляет стэйт. </p>\n\n\n\n<p id=\"c216\">Изменение стэйта означает то, что&nbsp;<code>render()</code>&nbsp;вызывается снова и отдаёт новый JSX, который заменяет предыдущий рендер.</p>\n\n\n\n<p id=\"e363\">Браузер показывает только второй рендер, чтобы избежать мигания промежуточных рендеров.</p>\n\n\n\n<p id=\"a4e1\"><a href=\"https://codesandbox.io/s/pensive-hamilton-p3qdd?from-embed=&amp;file=/src/index.js\">Вот пример</a></p>\n\n\n\n<p id=\"c56f\">Отлично, что это так работает когда нам это вообще нужно. Но большинство времени нам не нужен такой предоптимизированный подход, потому что мы делаем асинхронные запросы в сеть и уже потом выставляем стэйт после отрисовки экрана.</p>\n\n\n\n<p id=\"74ca\"><code>componentDidMount</code>&nbsp;и&nbsp;<code>useEffect</code>&nbsp;запускаются после монтирования. Но&nbsp;<code>useEffect</code>&nbsp;запускается после отрисовки экрана, а не как описано выше. Это говорит о том, что в этом случае будет мерцание экрана, если вам надо прочитать что-то из DOM и затем синхронно выставить стэйт, что сделать новый UI.</p>\n\n\n\n<p id=\"9a57\">Как же тогда получить старое поведение когда нам это нужно?</p>\n\n\n\n<p id=\"9786\"><code>useLayoutEffect</code>&nbsp;был разработан для того, чтобы иметь такой же тайминг как и у&nbsp;<code>componentDidMount</code>. В общем&nbsp;<code>useLayoutEffect(fn, [])</code>&nbsp;куда ближе по поведению к&nbsp;<code>componentDidMount()</code>, чем&nbsp;<code>useEffect(fn, [])</code>. Ну по крайней мере с точки зрения тайминга.</p>\n\n\n\n<h3 id=\"0339\"><strong>Означает ли это, что нам надо использовать useLayoutEffect?</strong></h3>\n\n\n\n<p id=\"7a50\">Возможно нет.</p>\n\n\n\n<p id=\"3734\">Если вы хотите избежать такого мерцания, синхронно выставляя стэйт, то используйте&nbsp;<code>useLayoutEffect</code>. Но так как это довольно редкий случай, то используйте просто&nbsp;<code>useEffect</code>.</p>\n\n\n\n<h2 id=\"ec04\"><strong>“Подхватывание” пропсов и стэйтов</strong></h2>\n\n\n\n<p id=\"a16b\">Применение асинхронного кода неизбежно в React. Когда такой код завершается, то значения полученных пропсов и стэйта могут быть немного смущающими. Давайте представим, что у нас был определенный асинхронный код, которому при отработке нужно было знать стэйт каунтера.</p>\n\n\n\n<pre class=\"wp-block-code javascript\"><code>class App extends React.Component {\n  state = {\n    count: 0\n  };\n\n  componentDidMount() {\n    longResolve().then(() => {\n      alert(this.state.count);\n    });\n  }\n\n  render() {\n    return (\n      &lt;div>\n        &lt;button\n          onClick={() => {\n            this.setState(state => ({ count: state.count + 1 }));\n          }}\n        >\n          Count: {this.state.count}\n        &lt;/button>\n      &lt;/div>\n    );\n  }\n}</code></pre>\n\n\n\n<p>При загрузке страницы у нас есть три секунды, чтобы кликнуть на кнопку несколько раз, перед тем как&nbsp;<code>longResolve</code>&nbsp;завершится. Далее выскочит алерт, говорящий актуальное значение&nbsp;<code>count</code>. С этим класс-компонентом, кликнув пять раз вы получите&nbsp;<code>5</code>&nbsp;в алерте.</p>\n\n\n\n<p>Теперь давайте отрефакторим это в хуках.</p>\n\n\n\n<pre class=\"wp-block-code javascript\"><code>function App() {\n  const &#91;count, setCount] = useState(0);\n\n  useEffect(() => {\n    longResolve().then(() => {\n      alert(count);\n    });\n  }, &#91;]);\n\n  return (\n    &lt;div>\n      &lt;button\n        onClick={() => {\n          setCount(count + 1);\n        }}\n      >\n        Count: {count}\n      &lt;/button>\n    &lt;/div>\n  );\n}</code></pre>\n\n\n\n<p><a href=\"https://codesandbox.io/s/bold-sinoussi-ff42j\">Поиграться</a></p>\n\n\n\n<p id=\"dbd1\">Кажется, что это отличный рефакторинг на хуки, но поведение тут будет другое. В этом примере вне зависимости от количества кликов вы будете получать&nbsp;<code>0</code>.</p>\n\n\n\n<p id=\"8e67\">Разница тут в том, что&nbsp;<code>useEffect</code>&nbsp;хватает значение каунтера когда он создаётся. Когда мы отдаем коллбэк к&nbsp;<code>useEffect</code>, он задерживается в памяти, где он только знает то, что&nbsp;<code>count</code>&nbsp;равен&nbsp;<code>0</code>&nbsp;когда был создан. С кодом на классах,&nbsp;<code>componentDidMount</code>&nbsp;не имеет замыканий в стэйте, так что он просто читает актуальное значение.</p>\n\n\n\n<p id=\"92b0\">Чтобы помочь это понять, представьте, что JS хранит коллбэк в памяти примерно так:</p>\n\n\n\n<pre class=\"wp-block-code javascript\"><code>// Это память, мне тут надо попридержать функцию. А и когда она создастся, там будут значения, такие как `count: 0`. Их надо тоже запомнить\n() => {\n// Хоть это и не так работает, но с точки зрения ментальной модели это как бы как будто count это просто переменная с указанным значением 0.\nconst count = 0\n  longResolve().then(() => {\n    alert(count);\n  });\n}</code></pre>\n\n\n\n<h3>Вот ещё один пример того, как&nbsp;<code>useEffect</code>&nbsp;захватывает значения.</h3>\n\n\n\n<p>В статье Дэна Абрамова&nbsp;<a href=\"https://overreacted.io/a-complete-guide-to-useeffect/\">“A Complete Guide to useEffect”</a>, примеры схожи и показывают поведение&nbsp;<code>setInterval</code>&nbsp;которое вы бы могли ожидать в классах, но не в хуках:</p>\n\n\n\n<pre class=\"wp-block-code javascript\"><code>// Версия на классах:\nclass App extends React.Component {\n  state = { count: 0 }\n\n  componentDidMount() {\n    setInterval(() => {\n      this.setState({ count: this.state.count + 1 })\n    }, 1000);\n  }\n\n  render() {\n    return &lt;div>{this.state.count}&lt;/div>\n  }\n}\n\n// То, что нам кажется эквивалентном на хуках:\nfunction App() { \n  const &#91;count, setCount] = useState(0)\n\n  useEffect(() => {\n    const id = setInterval(() => {\n      setCount(count + 1)\n    }, 1000);\n    return () => clearInterval(id)\n  }, &#91;])\n\n  return &lt;div>{count}&lt;/div>\n}</code></pre>\n\n\n\n<p id=\"6f20\">В примере с классом наш каунтер увеличивается каждую секунду. А в примере с хуками он увеличивается только с 0 до 1 и затем останавливается. Но тут интересно то, что сам интервал на самом деле не останавливается. Причиной такого поведения является то, что коллбэк&nbsp;<code>useEffect</code>&nbsp;как бы “захватывает” то, что он видит как&nbsp;<code>count</code>&nbsp;при создании. Этот колбэк всегда думает, что&nbsp;<code>count</code>&nbsp;равен&nbsp;<code>0</code>&nbsp;и следовательно мы всегда будем к нулю прибавлять единицу.</p>\n\n\n\n<p id=\"c158\">Если это кажется довольно сложным, то дайте ка я вам объясню как тут что происходит, это очень поможет вам в избежании будущих багов.</p>\n\n\n\n<p id=\"95c1\">Помните, что это не урок того, как работает&nbsp;<code>setInterval</code>&nbsp;с хуками, это скорее о том как применить ментальную модель с классов на хуки и о том как&nbsp;<code>useEffect</code>&nbsp;<em>“захватывает”</em>&nbsp;значения.</p>\n\n\n\n<p id=\"9a83\">Для начала нам надо понять почему массив с зависимостями так назван.</p>\n\n\n\n<blockquote class=\"wp-block-quote\"><p><em>Если ваш эффект “зависит” от чего то, то оно должно быть указано в вышеупомянутом массиве.</em></p></blockquote>\n\n\n\n<p>Наш код зависит от переменной c каунтером в стэйте. Вообще, нам надо было так сделать уже давным давно.</p>\n\n\n\n<pre class=\"wp-block-code javascript\"><code>useEffect(() => {\n    const id = setInterval(() => {\n      setCount(count + 1)\n    }, 1000)\n    return () => clearInterval(id)\n  }, &#91;count])</code></pre>\n\n\n\n<p>Теперь код работает так, как нам надо, потому что мы хотим, чтобы коллбэк в&nbsp;<code>useEffect</code>&nbsp;запускался только тогда, когда каунт меняется. Когда это происходит, то предыдущий коллбэк в памяти очистится и следовательно&nbsp;<code>setInterval</code>&nbsp;будет удален. Затем React создаст совершенно новый callback в памяти, который уже будет знать, что count равен&nbsp;<code>1</code>.</p>\n\n\n\n<pre class=\"wp-block-code javascript\"><code>// Эй память, нам тут надо функцию сохранить...\n() => {\n  const count = 0\n  const id = setInterval(() => {\n    setCount(count + 1)\n  }, 1000)\n  return () => clearInterval(id)}\n\n// Потом при смене каунта...\n// Эй память, очисти первую функцию  \n// и потом нам надо захостить вторую...\n() => {\n  const count = 1\n  const id = setInterval(() => {\n    setCount(count + 1)\n  }, 1000)\n  return () => clearInterval(id)\n}</code></pre>\n\n\n\n<p>Если мы хотим узнать актуальное значение, а не которое “захватили”, то вы всегда можете&nbsp;<a href=\"https://reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables\">использовать рефы</a>.</p>\n\n\n\n<p>Я думаю, что пример с&nbsp;<code>setInterval</code>&nbsp;это отличный способ понять суть “<em>захватывания</em>” в&nbsp;<code>useEffect</code>, хотя стоит отметить, что есть ещё одно API для указания стэйта, в котором мы передаём функцию, а не значение. React вызовет функцию с существующим стэйтом:</p>\n\n\n\n<pre class=\"wp-block-code javascript\"><code>useEffect(() => {\n  const id = setInterval(() => {\n    // Когда мы передаем функцию, React вызывает её с\n    // актуальным стейтом и всякий раз в этом случае она же и становится актуальным стэйтом.\n    setCount(count => count + 1)\n  }, 1000)\n  return () => clearInterval(id)\n}, &#91;])</code></pre>\n\n\n\n<p>Теперь наш эффект не “стесняет” переменную&nbsp;<code>count</code>&nbsp;и теперь нам не нужно добавлять массив с зависимостями, и мы не стесняем его, следовательно ничего не указывается как исходное значение при запуске эффекта. Хоть это уже немного и отклонение от основной темы, но сейчас мы хотя бы понимаем суть “захватывания” для эффектов и то, что есть другой способ выставить стэйт — будем надеятся, что это&nbsp;<strong>win-win</strong>&nbsp;при обучении хукам.</p>\n\n\n\n<h2 id=\"d6e5\"><strong>“Захват” это хорошо или плохо?</strong></h2>\n\n\n\n<p id=\"f13f\">Есть некоторые баги, которых можно избежать во время использования “<em>захвата</em>”, а не актуального значения. Посмотрите этот пример от Дэна Абрамова&nbsp;<a href=\"https://codesandbox.io/s/pjqnl16lm7\">https://codesandbox.io/s/pjqnl16lm7</a>, где он показывает как “<em>захват</em>” отдаёт вам ожидаемое поведение с поведением компонента класса, который отдаёт актуальный стэйт. В этом примере мы можем подписаться на кого-нибудь и затем быстро менять профайлы. Когда мы меняем профайлы перед получением ответа из сети, существует баг, когда показывается имя нашего последнего подписчика. Это баг с версией на классах, а не с версией на хуках. Почитайте&nbsp;<a href=\"https://overreacted.io/how-are-function-components-different-from-classes/\">тут</a>&nbsp;про это.</p>\n\n\n\n<h2 id=\"56a3\"><strong>Что там с рефакторингом с классов на хуки?</strong></h2>\n\n\n\n<p id=\"4223\">Предположим, что вы написали такой код:</p>\n\n\n\n<pre class=\"wp-block-code javascript\"><code>class UserProfile extends React.Component {\n  state = { user: null }\n\n  componentDidMount() {\n    getUser(this.props.uid).then(user => {\n      this.setState({ user })\n    })\n  }\n\n  render() {\n    // ...\n  }\n}</code></pre>\n\n\n\n<p id=\"d2cd\">Видите баг?</p>\n\n\n\n<p id=\"2559\">Что случиться если проп&nbsp;<code>uid</code>&nbsp;изменится? Мы не увидим нового пользователя, потому что мы не обрабатываем это изменение в&nbsp;<code>componenDidUpdate</code>. Обычно, если&nbsp;<code>componentDidMount</code>&nbsp;делает сайд-эффекты, зависящие от пропсов или стэйта, то вам в помощь тогда придет&nbsp;<code>componentDidUpdate</code>, чтобы обработать сайд-эффект уже тогда, когда проп или стэйт изменится. Но иногда получается не совсем так идеально и мы можем напороться на баг, забыв о вышеописанном.</p>\n\n\n\n<p id=\"88c1\">Если вы делаете рефакторинг с классов на хуки и вы просто меняете&nbsp;<code>componentDidMount</code>&nbsp;на&nbsp;<code>useEffect</code>&nbsp;с пустым массивом зависимостей, то вы почти всегда будете ловить баги в новом коде. Давайте представим, что мы отрефакторили код выше в это:</p>\n\n\n\n<pre class=\"wp-block-code javascript\"><code>function UserProfile({ uid }) {\n  const &#91;user, setUser] = useState(null)\n\n  useEffect(() => {\n    getUser(uid).then(user => {\n      setUser(user)\n    })\n  }, &#91;]) // buggy without `uid` in this array\n  // ...\n}</code></pre>\n\n\n\n<p id=\"af23\">Этот код будет работать как надо до тех пор, пока у&nbsp;<code>UserProfile</code>&nbsp;не поменяется пропс&nbsp;<code>uid</code>. Если у вас уже на борту есть дополнительные правила по листингу хуков, то вы будете получать предупреждения пока не добавите&nbsp;<code>[uid]</code>, как массив зависимостей. Учитывая это, версия с хуками будет делать тоже самое, что делали бы&nbsp;<code>componentDidMount&nbsp;</code>и&nbsp;<code>componentDidUpdate</code>. В общем вы видите, что сам вопрос&nbsp;<em>“Является ли useEffect с пустым массивом зависимостей новой версией componentDidMount?”</em>&nbsp;Сам по себе некорректен и тут стоит сразу начинать с того, что&nbsp;<code>componentDidMount</code>&nbsp;зачастую просто не может быть отрефакторен в&nbsp;<code>useEffect(fn, [])</code>.</p>\n\n\n\n<p id=\"c9d6\">К слову, не забудь сделать функцию очистки. Это предостережет от багов выставления стэйта на демонтированный компонент и выставление старого стэйта, когда&nbsp;<code>uid</code>&nbsp;изменяется.</p>\n\n\n\n<pre class=\"wp-block-code javascript\"><code>useEffect(() => {\n  let isCurrent = true\n  getUser(uid).then(user => {\n    if (isCurrent) {\n      setUser(user)\n    }\n  })\n  return () => {\n    isCurrent = false\n  }\n}, &#91;uid])</code></pre>\n\n\n\n<h2 id=\"1c08\"><strong>Заключение</strong></h2>\n\n\n\n<p id=\"0931\">Думать во временных границах это то, как мы делали с компонентами класса. Теперь нам надо думать “<em>как выглядит UI с этим стейтом?</em>” и “<em>когда стэйт изменяется, какие сайд-эффекты надо перезапускать.</em>” Попробуйте отталкиваться от стэйта, а не от “<em>тайминга жизненного цикла</em>” компонента.</p>\n","type":"js","date":"2021-01-11T14:17:28.000Z","acf":{"seo_desc":"Они почти одинаковые. Но существует достаточно различий, которые возможно доставят вам неудобства, особенно если вы делаете рефакторинг с классов.","keywords":"componentDidMount, useEffect, разница между componentDidMount и useEffect, useEffect это не componentDidMount ","pub_date":"2020-08-12","tags":"React, useEffect, JavaScript, React Hooks"},"title":"useEffect(fn, []) это не новый componentDidMount()","id":"14236f35-b58a-56dc-a1cc-4d6b44cc05d1","excerpt":"<p>Они почти одинаковые. Но существует достаточно различий, которые возможно доставят вам неудобства, особенно если вы делаете рефакторинг с классов.</p>\n","featured_media":{"source_url":"https://stasonmars.ru/wp/wp-content/uploads/2021/01/fjdnsjklfnsdjklnfsknlj.jpg"},"interesting":[{"title":"Погружаемся в работу с children на React","slug":"pogruzhaemsya-v-raboty-s-children-na-react","type":"js"},{"title":"Подробно о React Router. Часть 3— редиректы, передача props компоненту роута и вложенные роуты +бонус","slug":"react-router-chast-3-redirekty-peredacha-props-komponentu-routa-i-vlozhennie-routy-bonus","type":"js"},{"title":"12 лучших и простых практик в React, которых стоит придерживаться в 2020 году","slug":"12-luchih-i-prosih-praktik-v-react-kotorih-stoit-priderzhivatsya-v-2020-godu","type":"js"}]}},"staticQueryHashes":["3576131997","63159454"]}