Upgrade to Pro — share decks privately, control downloads, hide ads and more …

React Hooks: Iteractivity in Functional Components (RU)

Alexey Taktarov
September 26, 2023
26

React Hooks: Iteractivity in Functional Components (RU)

Guest lecture for the Web Development class at the SFU university with live coding demos. React Hooks overview: from useState to useImperativeHandle. How rendering in React works under the hood.

The final demo of this lecture was a simplified Instagram stories look-alike app.

Alexey Taktarov

September 26, 2023
Tweet

Transcript

  1. чему мы научимся к концу лекции? писать полноценные React-приложения без

    использования классовых компонентов class App extends React.Component 2
  2. практическая цель на сегодня сделать веб-клон Instragram Stories с помощью

    только хуков и функциональных 
 компонентов
  3. 5 Алексей Тактаров / @mlfrg 2014 прикладная математика и информатика

    мехмата ЮФУ ( McS ) 2009 – 2013 разрабатывал многопоточные, сетевые и системные приложения на Си/Си++ *люблю оптимизировать код и бороться за каждый байт
  4. 6 2012 : впервые попробовал Node.js и понял, что хочу

    делать штуки для веба работает на любом устройстве, развитая система пакетов, минималистичный синтаксис JavaScript, …
  5. 7 экспериментировал с анимациями на Canvas и написал свою первую

    open-source библиотеку на JS (провально) → https://codepen.io/molefrog → https://molefrog.com/rye
  6. 8 2014-сейчас: работаю в стартапах помогаю быстро начать и масштабироваться

    
 по мере роста компании 
 фокус: архитектура бекенда и фронтенда, UI-киты, инструменты разработки, культура разработки чтобы разработчики были продуктивными и гибкими
  7. 9 как технический директор · 2014 – 2016 Смартомато: SaaS

    для ресторанной доставки / Ember.js, Rails
  8. 13 resume.io: запуск запустили MVP за месяц делали все подряд:

    рисовали, программировали, писали тексты выбрали React для фронтенда 
 (но лишь через год после запуска! )
  9. 14 resume.io сейчас 16M резюме, 25K + регистраций/день remote-first команда:

    Россия, Нидерланды, США Rails-монолит, React-приложение и 
 4 фронтенд-микросервиса
  10. 15 → https://www.talentinc.com/press-2021 - 06 - 09 🥳 в июне

    ’21 нас поглотил американский Talent Inc.
  11. 16 Мы — UI-перфекционисты пользователи ценят скорость заполнения резюме в

    редакторе и 
 внимание к деталям React отлично для этого подходит 16 “SO easy to use! The editor and text field functionality is so smooth, and layouts look great. I LOVE THIS, it's a game changer, THANK YOU!”
  12. 16 Мы — UI-перфекционисты пользователи ценят скорость заполнения резюме в

    редакторе и 
 внимание к деталям React отлично для этого подходит 16 “SO easy to use! The editor and text field functionality is so smooth, and layouts look great. I LOVE THIS, it's a game changer, THANK YOU!”
  13. рассмотрим статичное приложение нет обновлений = неитерактивно компоненты детерминированы: только

    props влияют на результат рендера «функциональные компоненты» Herman Walton financial analyst Useful Services Ltd. + 1 842 11 - 1337
  14. рассмотрим статичное приложение нет обновлений = неитерактивно компоненты детерминированы: только

    props влияют на результат рендера «функциональные компоненты» Herman Walton financial analyst Useful Services Ltd. + 1 842 11 - 1337 <BusinessCard />
  15. Herman Walton financial analyst Useful Services Ltd. + 1 842

    11 - 1337 <Header / > <Footer / > -> https://reactjs.org/docs/thinking-in-react.html
  16. Herman Walton financial analyst Useful Services Ltd. + 1 842

    11 - 1337 <Company name="Useful . .. " />
  17. 27 const Company = props = > <div>{props.name} </ div>

    функциональные компоненты отлично подходят для статичных приложений: блогов, лендингов, PDF-резюме* и т.д. * Сергей Авдяков, Рендеринг PDF : история одного продукта
  18. 28 const Company = props = > <div>{props.name} </ div>

    const Company = (props) => { return <div>{props.name} </ div> } function Company(props) { return <div>{props.name} </ div> } функциональные компоненты могут быть определены как обычные функции, так и как анонимные arrow functions
  19. 30 class Clock extends React.Component { constructor(props) { super(props) this.state

    = { showWarning: false } } handleClick() { this.setState({ showWarning: true }) } render() { /* .. . * / } } ← инициализация ← обновление
  20. 31 за state отвечает сам компонент за props — компонент

    сверху изменение обоих приводит к перерисовке (rerender)
  21. 33 <App /> <Navigation /> <ResumeEditor /> <Menu /> <Links

    /> <PersonalInfo /> <input /> <label />
  22. 33 <App /> <Navigation /> <ResumeEditor /> <Menu /> <Links

    /> <PersonalInfo /> <input /> <label /> “change” event
  23. 33 <App /> <Navigation /> <ResumeEditor /> <Menu /> <Links

    /> <PersonalInfo /> <input /> <label /> “change” event setState({ name: “Ale” })
  24. 34 <App /> <Navigation /> <ResumeEditor /> <Menu /> <Links

    /> <PersonalInfo /> <input /> <label />
  25. 35 <App /> <Navigation /> <ResumeEditor /> <Menu /> <Links

    /> <PersonalInfo /> <input /> <label />
  26. 36 <App /> <Navigation /> <ResumeEditor /> <Menu /> <Links

    /> <PersonalInfo /> <input /> <label />
  27. 37 const ResumeEditor = props => { … } хуки

    дают позволяют функциональным компонентам 
 иметь побочные эффекты state, lifecycle-методы и др.
  28. 39 появились в React 16.8.0 (февраль 2019 ) результат многих

    лет разработки самого Реакта и 
 опыта реальных приложений React Hooks
  29. 40 причина #1 : классы — это сложно! они плохо

    оптимизируются компиляторами и занимают много КБ
  30. 42 class ModalDialog extends React.Component { constructor(props) { super(props) this.state

    = { isActive: false, userName: "Alex" } } componentDidMount() { subscribeToUserUpdates( .. . ) subscribeToClickOutside( . .. ) } componentWillUnmount() { unsubscribeFromUserUpdates( . .. ) unsubscribeFromClickOutside( ... ) } render() { … } }
  31. 42 class ModalDialog extends React.Component { constructor(props) { super(props) this.state

    = { isActive: false, userName: "Alex" } } componentDidMount() { subscribeToUserUpdates( .. . ) subscribeToClickOutside( . .. ) } componentWillUnmount() { unsubscribeFromUserUpdates( . .. ) unsubscribeFromClickOutside( ... ) } render() { … } }
  32. 42 class ModalDialog extends React.Component { constructor(props) { super(props) this.state

    = { isActive: false, userName: "Alex" } } componentDidMount() { subscribeToUserUpdates( .. . ) subscribeToClickOutside( . .. ) } componentWillUnmount() { unsubscribeFromUserUpdates( . .. ) unsubscribeFromClickOutside( ... ) } render() { … } }
  33. 43 позволяют использовать React без классов позволяют разбивать компонент на

    множество переиспользуемых функций (хуки! ) разбиение производят по областям бизнес-логики (domain) React Hooks
  34. 45 import { useState } from "react" const [user, setUser]

    = useState("Alex") → https://reactjs.org/docs/hooks-reference.html#usestate
  35. 45 import { useState } from "react" const [user, setUser]

    = useState("Alex") → https://reactjs.org/docs/hooks-reference.html#usestate деструктуризация массива (array destructuring)
  36. 46 const [a, b] = [1,2,3] // a: 1, b:

    2 const [a, b, ... rest] = [1,2,3,4,5] / / a: 1, b: 2, rest: [4,5] const [a, ,b] = [1,2,3] // a: 1, b: 3 const [, x] = [1,2,3] / / x: ? const [, x] = [1] // x: ? → https://exploringjs.com/es6/ch_destructuring.html
  37. 47 import { useState } from "react" const App =

    () => { const [user, setUser] = useState("Alex") / / setUser(“Menno") return `Hi, ${user}!` } class App extends React.Component { constructor() { super(props) this.state = { user: "Alex" } } render() { return `Hi, ${this.state.user}!` } / / this.setState("Menno") }
  38. 47 import { useState } from "react" const App =

    () => { const [user, setUser] = useState("Alex") / / setUser(“Menno") return `Hi, ${user}!` } class App extends React.Component { constructor() { super(props) this.state = { user: "Alex" } } render() { return `Hi, ${this.state.user}!` } / / this.setState("Menno") } инициализация
  39. 48 import { useState } from "react" const App =

    () => { const [user, setUser] = useState("Alex") / / setUser(“Menno") return `Hi, ${user}!` } class App extends React.Component { constructor() { super(props) this.state = { user: "Alex" } } render() { return `Hi, ${this.state.user}!` } / / this.setState("Menno") } значение
  40. 49 import { useState } from "react" const App =

    () => { const [user, setUser] = useState("Alex") / / setUser(“Menno") return `Hi, ${user}!` } class App extends React.Component { constructor() { super(props) this.state = { user: "Alex" } } render() { return `Hi, ${this.state.user}!` } / / this.setState("Menno") } сеттер
  41. 50 import { useState } from "react" const App =

    () => { const [user, setUser] = useState("Alex") setUser("Menno") return `Hi, ${user}!` } ошибка: бесконечный цикл ререндера! 💥
  42. 51 как это работает с обычными функциями? “хуки — это

    не магия, просто массивы” → https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e
  43. 52 const App = () => { const [value, setValue]

    = useState(1) 
 const [name, setName] = useState("Alex") const increment = () => setValue(x => x + 1) return <button onClick={increment}>{value} </ button> } state storage 0 1 2 3 4
  44. 53 const App = () => { const [value, setValue]

    = useState(1) 
 const [name, setName] = useState("Alex") const increment = () => setValue(x => x + 1) return <button onClick={increment}>{value} </ button> } 1 state storage 0 1 2 3 4
  45. 54 const App = () => { const [value, setValue]

    = useState(1) 
 const [name, setName] = useState("Alex") const increment = () => setValue(x => x + 1) return <button onClick={increment}>{value} </ button> } 1 "Alex" state storage 0 1 2 3 4
  46. 55 const App = () => { const [value, setValue]

    = useState(1) 
 const [name, setName] = useState("Alex") const increment = () => setValue(x => x + 1) return <button onClick={increment}>{value} </ button> } 1 "Alex" state storage 0 1 2 3 4
  47. 56 const App = () => { const [value, setValue]

    = useState(1) 
 const [name, setName] = useState("Alex") const increment = () => setValue(x => x + 1) return <button onClick={increment}>{value} </ button> } 2 "Alex" state storage 0 1 2 3 4
  48. 57 const App = () => { const [value, setValue]

    = useState(1) 
 const [name, setName] = useState("Alex") const increment = () => setValue(x => x + 1) return <button onClick={increment}>{value} </ button> } 2 "Alex" state storage 0 1 2 3 4
  49. 58 специфика внутренней реализации хуков накладывает особые «ограничения» на разработчиков

    правила использования! хуки можно использовать только внутри компонентов определять хуки только в самом начале метода порядок нельзя менять (нельзя вызывать внутри if-else, циклов и т.д.)
  50. 59 демо #1 двоичный сумматор 0 + 0 = 00

    0 + 1 = 01 1 + 0 = 01 1 + 1 = 10 | | _ sum bit 
 | 
 carry bit будет полезно 
 
 руководство по битовым операциям в JS 
 -> Practical bit manipulation in JavaScript
  51. 61 import { useEffect } from "react" const App =

    () => { useEffect(() => { /* effect body */ }, [a, b, c]) } ← массив зависимостей useEffect позволяет выполнять побочные эффекты в компоненте по условию
  52. 62 как useEffect работает с зависимостями A B C D

    A A A A A B A B каждый рендер 
 mount + update undefined [] [x, y] mount mount + x или y поменялись
  53. 63 для чего следует применять useEffect? для выполнения императивных эффектов:

    проиграть звук, перейти в полноэкранный режим, порисовать на <canvas> … для подписки на внешний источник данных (вне Реакта): веб-сокеты, изменение размера окна, переход в оффлайн-режим … для расчёта state на основе props сверху
  54. 64 демо #2 обновляем заголовок страницы с помощью useEffect (без

    зависимостей) → https://developer.mozilla.org/en-US/docs/Web/API/Document/title document.title = "Hi!"
  55. 65 демо #3 подписка на Page Visibility (с пустым массивом

    зависимостей) → https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API document.visibilityState // "hidden" or "visible"
  56. import { useState, useEffect } from "react" function Greeting() {

    const [name, setName] = useState("") useEffect(() => { const handler = user => setName(user.name) User.on('update', handler) return () => User.off('update', handler) }, []) return `Hi, ${name}!` }
  57. import { useState, useEffect } from "react" function Greeting() {

    const [name, setName] = useState("") useEffect(() => { const handler = user => setName(user.name) User.on('update', handler) return () => User.off('update', handler) }, []) return `Hi, ${name}!` } 1
  58. import { useState, useEffect } from "react" function Greeting() {

    const [name, setName] = useState("") useEffect(() => { const handler = user => setName(user.name) User.on('update', handler) return () => User.off('update', handler) }, []) return `Hi, ${name}!` } 1 1 подписка на событие
  59. import { useState, useEffect } from "react" function Greeting() {

    const [name, setName] = useState("") useEffect(() => { const handler = user => setName(user.name) User.on('update', handler) return () => User.off('update', handler) }, []) return `Hi, ${name}!` } 1 1 подписка на событие 2
  60. import { useState, useEffect } from "react" function Greeting() {

    const [name, setName] = useState("") useEffect(() => { const handler = user => setName(user.name) User.on('update', handler) return () => User.off('update', handler) }, []) return `Hi, ${name}!` } 1 1 подписка на событие 2 обработчик может возвращать функцию для очистки эффектов: отписки от события 2
  61. import { useState, useEffect } from "react" function Greeting() {

    const [name, setName] = useState("") useEffect(() => { const handler = user => setName(user.name) User.on('update', handler) return () => User.off('update', handler) }, []) return `Hi, ${name}!` } 1 1 подписка на событие 2 обработчик может возвращать функцию для очистки эффектов: отписки от события 2 3
  62. import { useState, useEffect } from "react" function Greeting() {

    const [name, setName] = useState("") useEffect(() => { const handler = user => setName(user.name) User.on('update', handler) return () => User.off('update', handler) }, []) return `Hi, ${name}!` } 1 1 подписка на событие 2 обработчик может возвращать функцию для очистки эффектов: отписки от события 2 3 3 важно: обновлять стейт при изменении данных, иначе компонент не перерисуется!
  63. useEffect(() => { document.title = `You clicked ${count} times` },

    [count]) 68 с помощью массива зависимостей можно избежать вычислений «впустую»
  64. useEffect: правила «хорошего тона» «убирай за собой»: отписываться от эффектов

    через return () = > { / * unsubscribe */ } указывать полный список зависимостей 
 [a, b, c] можно и нужно иметь несколько useEffect в одном компоненте под каждую задачу const App = (props) => { const [count, setCount] = useState(0) useEffect(() => { document.title = `${count} clicks` }, [title]) const [isOnline, setIsOnline] = useState(null) useEffect(() => { function handler(status) { setIsOnline(status.isOnline) } subscribe(props.friendId, handler) return () => { unsubscribe(props.friendId, handler) } }, [props.friendId]) }
  65. 71 хук useRef — локальные переменные в 
 функциональных компонентах

    (аналог this.*) const ref = useRef(null) if (!ref.current) ref.current = "alex"
  66. 71 хук useRef — локальные переменные в 
 функциональных компонентах

    (аналог this.*) const ref = useRef(null) if (!ref.current) ref.current = "alex" ref — это объект вида { current: null }
  67. 71 хук useRef — локальные переменные в 
 функциональных компонентах

    (аналог this.*) const ref = useRef(null) if (!ref.current) ref.current = "alex" ref — это объект вида { current: null } ref — локальная переменная, 
 поэтому такое не сработает: ref = 41
  68. 73 через useRef можно сохранить ссылку на DOM-элемент используя свойство

    ref примитивных JSX элементов Реакт автоматически запишет в current ссылку на элемент function AudioPlayer() { const audioElement = useRef() const play = () => { audioElement.current.play() } return <audio src=" .. . " ref={audioElement} /> }
  69. 74 демо #4 работа с canvas (конфетти! ) → https://github.com/catdad/canvas-confetti

    import Confetti from 'canvas-confetti' const launcher = Confetti.create(canvas) launcher({ particleCount: 100, spread: 160, startVelocity: 30 })
  70. 75 useRef удобен для хранения вспомогательных объектов, которые нужно инициализировать

    только один раз мы могли бы создавать один confettiLauncher на компонент
  71. 77 хук useMemo — мемоизирует вычисления на основе зависимостей const

    data = useMemo( () => decrypt(message, key), [message, key] ) алгоритм инвалидации такой же, как и в useEffect
  72. 78 const carry = lhs & rhs const carry =

    useMemo(() => lhs & rhs, [lhs, rhs])
  73. 80 const App = (props) => { useEffect(() => {

    document.title = props.title }, [props.title]) }
  74. 81 const useDocumentTitle = title = > { useEffect(() =>

    { document.title = title }, [title]) } const App = (props) => { useDocumentTitle(props.title) } наш собственный хук, название use* важнейшее свойство хуков: из них можно строить новые хуки (для переиспользования логики)
  75. 83 components композиция и переиспользование уровня представления (view) hooks композиция

    и переиспользование уровня состояния и логики useSpring() react-spring useDebounce() use-debounce useSound() use-sound <Slider / > antd 
 
 <PdfViewer / > @react-pdf-viewer 
 <ColorPicker /> react-colorful
  76. 84 для дальнейшего изучения дополнительные ресурсы шрифты Inter и Jet

    Brains Mono · иллюстрация Катя Симачёва Алексей Тактаров · molefrog.com · github.com/molefrog · @mlfrg React Docs: Introducing Hooks Collection of React Hooks useHooks() — easy to understand React Hooks recipes Дайте волю хукам! Пишем миниатюрный роутер.