$30 off During Our Annual Pro Sale. View Details »

React Hooks: Iteractivity in Functional Components (RU)

Alexey Taktarov
September 26, 2023
9

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. Алексей Тактаров · @mlfrg
    React Hooks: интерактивность


    в функциональных компонентах

    View Slide

  2. чему мы научимся к концу лекции?
    писать полноценные React-приложения без
    использования классовых компонентов
    class App extends React.Component
    2

    View Slide

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

    View Slide

  4. практическая цель на сегодня
    сделать веб-клон Instragram Stories
    с помощью только хуков и функциональных

    компонентов

    View Slide

  5. 00. о сегодняшнем лекторе

    View Slide

  6. 5
    Алексей Тактаров / @mlfrg
    2014 прикладная математика и информатика мехмата ЮФУ
    (
    McS
    )

    2009

    2013 разрабатывал многопоточные, сетевые и системные
    приложения на Си/Си++
    *люблю оптимизировать код и бороться за каждый байт

    View Slide

  7. 6
    2012
    :
    впервые попробовал Node.js и


    понял, что хочу делать штуки для веба
    работает на любом устройстве, развитая система
    пакетов, минималистичный синтаксис JavaScript, …

    View Slide

  8. 7
    экспериментировал с анимациями на Canvas и написал


    свою первую open-source библиотеку на JS (провально)
    → https://codepen.io/molefrog
    → https://molefrog.com/rye

    View Slide

  9. 8
    2014-сейчас: работаю в стартапах
    помогаю быстро начать и масштабироваться

    по мере роста компании

    фокус: архитектура бекенда и фронтенда, UI-киты,
    инструменты разработки, культура разработки
    чтобы разработчики были продуктивными и гибкими

    View Slide

  10. 9
    как технический директор · 2014

    2016
    Смартомато: SaaS для ресторанной доставки / Ember.js, Rails

    View Slide

  11. 10
    как UI-дизайнер и разработчик · 2017
    Shogun: блочный редактор веб-страниц / Rails, React

    View Slide

  12. 11
    Shogun продолжают расти

    View Slide

  13. 12
    как сооснователь и тех. директор · 2017–сейчас
    resume.io: онлайн редактор резюме / Rails, React

    View Slide

  14. 13
    resume.io: запуск
    запустили MVP за месяц


    делали все подряд: рисовали,
    программировали, писали тексты


    выбрали React для фронтенда

    (но лишь через год после запуска!
    )

    View Slide

  15. 14
    resume.io сейчас
    16M резюме, 25K
    +
    регистраций/день


    remote-first команда: Россия,
    Нидерланды, США


    Rails-монолит, React-приложение и

    4 фронтенд-микросервиса

    View Slide

  16. 15
    → https://www.talentinc.com/press-2021
    -
    06
    -
    09
    🥳 в июне ’21 нас поглотил американский Talent Inc.

    View Slide

  17. 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!”


    View Slide

  18. 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!”


    View Slide

  19. 17
    в свободное от проекта время
    контрибьютор нескольких open-source проектов:

    микро-библиотеки для React

    View Slide

  20. → https://github.com/molefrog/wouter
    wouter
    микро-роутер, всего
    ~
    1400 байт


    написан полностью на React Hooks


    3,500
    +
    звездочек на GH

    View Slide

  21. 01. вспоминаем, как работает React

    View Slide

  22. 20
    React — библиотека для описания
    пользовательских интерфейсов
    (
    UI
    )

    View Slide

  23. 21
    UI строится с помощью
    переиспользуемых компонентов
    принцип Don’t Repeat Yourself
    =
    DRY

    View Slide

  24. 22
    вид и поведение компонентов
    специализируются при помощи props
    />


    View Slide

  25. 23
    приложение = иерархия компонентов
    компонент верхнего уровня (root) рисует (render)
    дочерние компоненты

    View Slide

  26. рассмотрим статичное приложение
    нет обновлений = неитерактивно


    компоненты детерминированы: только
    props влияют на результат рендера


    «функциональные компоненты»
    Herman Walton
    financial analyst
    Useful Services Ltd.
    +
    1 842 11
    -
    1337

    View Slide

  27. рассмотрим статичное приложение
    нет обновлений = неитерактивно


    компоненты детерминированы: только
    props влияют на результат рендера


    «функциональные компоненты»
    Herman Walton
    financial analyst
    Useful Services Ltd.
    +
    1 842 11
    -
    1337
    />

    View Slide

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

    View Slide

  29. Herman Walton
    financial analyst
    Useful Services Ltd.
    +
    1 842 11
    -
    1337
    />

    View Slide

  30. 27
    const Company = props
    = >
    {props.name}

    div>
    функциональные компоненты отлично подходят для статичных
    приложений: блогов, лендингов, PDF-резюме* и т.д.
    * Сергей Авдяков, Рендеринг PDF
    :
    история одного продукта

    View Slide

  31. 28
    const Company = props
    = >
    {props.name}

    div>
    const Company = (props)
    =>
    {

    return {props.name}

    div>

    }
    function Company(props) {

    return {props.name}

    div>

    }
    функциональные компоненты могут быть определены как обычные функции,


    так и как анонимные arrow functions

    View Slide

  32. 29
    как React-приложения становятся
    интерактивными?

    View Slide

  33. 29
    как React-приложения становятся
    интерактивными?
    за счёт изменения состояния компонентов (state)

    View Slide

  34. 30
    class Clock extends React.Component {

    constructor(props) {

    super(props)

    this.state = { showWarning: false }

    }

    handleClick() {

    this.setState({ showWarning: true })

    }

    render() {

    /* .. . * /


    }

    }

    ← инициализация
    ← обновление

    View Slide

  35. 31
    за state отвечает сам компонент


    за props — компонент сверху


    изменение обоих приводит к перерисовке (rerender)

    View Slide

  36. 32
    state — единственный источник обновлений UI
    и точка!

    View Slide

  37. 33
    />
    />
    />
    />
    />
    />
    />
    />

    View Slide

  38. 33
    />
    />
    />
    />
    />
    />
    />
    />
    “change” event

    View Slide

  39. 33
    />
    />
    />
    />
    />
    />
    />
    />
    “change” event
    setState({ name: “Ale” })

    View Slide

  40. 34
    />
    />
    />
    />
    />
    />
    />
    />

    View Slide

  41. 35
    />
    />
    />
    />
    />
    />
    />
    />

    View Slide

  42. 36
    />
    />
    />
    />
    />
    />
    />
    />

    View Slide

  43. 37
    const ResumeEditor = props
    =>
    { … }
    хуки дают позволяют функциональным компонентам

    иметь побочные эффекты
    state, lifecycle-методы и др.

    View Slide

  44. 02. React Hooks: мотивация

    View Slide

  45. 39
    появились в React 16.8.0 (февраль 2019
    )

    результат многих лет разработки самого Реакта и

    опыта реальных приложений
    React Hooks

    View Slide

  46. 40
    причина #1
    :
    классы — это сложно!
    они плохо оптимизируются компиляторами и занимают много КБ

    View Slide

  47. 41
    причина #2
    :
    логику в классах неудобно переиспользовать
    решения: HOCs и render-props — непросто!

    View Slide

  48. 42
    class ModalDialog extends React.Component {

    constructor(props) {

    super(props)

    this.state = {

    isActive: false,

    userName: "Alex"

    }

    }

    componentDidMount() {

    subscribeToUserUpdates(
    ..
    .
    )

    subscribeToClickOutside(
    .
    ..
    )

    }

    componentWillUnmount() {

    unsubscribeFromUserUpdates(
    .
    ..
    )

    unsubscribeFromClickOutside(
    ...
    )

    }

    render() { … }

    }

    View Slide

  49. 42
    class ModalDialog extends React.Component {

    constructor(props) {

    super(props)

    this.state = {

    isActive: false,

    userName: "Alex"

    }

    }

    componentDidMount() {

    subscribeToUserUpdates(
    ..
    .
    )

    subscribeToClickOutside(
    .
    ..
    )

    }

    componentWillUnmount() {

    unsubscribeFromUserUpdates(
    .
    ..
    )

    unsubscribeFromClickOutside(
    ...
    )

    }

    render() { … }

    }

    View Slide

  50. 42
    class ModalDialog extends React.Component {

    constructor(props) {

    super(props)

    this.state = {

    isActive: false,

    userName: "Alex"

    }

    }

    componentDidMount() {

    subscribeToUserUpdates(
    ..
    .
    )

    subscribeToClickOutside(
    .
    ..
    )

    }

    componentWillUnmount() {

    unsubscribeFromUserUpdates(
    .
    ..
    )

    unsubscribeFromClickOutside(
    ...
    )

    }

    render() { … }

    }

    View Slide

  51. 43
    позволяют использовать React без классов


    позволяют разбивать компонент на множество
    переиспользуемых функций (хуки!
    )

    разбиение производят по областям бизнес-логики (domain)
    React Hooks

    View Slide

  52. 03. хук useState

    View Slide

  53. 45
    import { useState } from "react"

    const [user, setUser] = useState("Alex")

    → https://reactjs.org/docs/hooks-reference.html#usestate

    View Slide

  54. 45
    import { useState } from "react"

    const [user, setUser] = useState("Alex")

    → https://reactjs.org/docs/hooks-reference.html#usestate
    деструктуризация массива (array destructuring)

    View Slide

  55. 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

    View Slide

  56. 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")

    }

    View Slide

  57. 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")

    }

    инициализация

    View Slide

  58. 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")

    }

    значение

    View Slide

  59. 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")

    }

    сеттер

    View Slide

  60. 50
    import { useState } from "react"

    const App = ()
    =>
    {

    const [user, setUser] = useState("Alex")



    setUser("Menno")

    return `Hi, ${user}!`

    } ошибка: бесконечный цикл ререндера!
    💥

    View Slide

  61. 51
    как это работает с обычными
    функциями?
    “хуки — это не магия, просто массивы”
    → https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e

    View Slide

  62. 52
    const App = ()
    =>
    {

    const [value, setValue] = useState(1)

    const [name, setName] = useState("Alex")

    const increment = ()
    =>
    setValue(x
    =>
    x + 1)

    return {value}

    button>

    }

    state storage
    0 1 2 3 4

    View Slide

  63. 53
    const App = ()
    =>
    {

    const [value, setValue] = useState(1)

    const [name, setName] = useState("Alex")

    const increment = ()
    =>
    setValue(x
    =>
    x + 1)

    return {value}

    button>

    }

    1
    state storage
    0 1 2 3 4

    View Slide

  64. 54
    const App = ()
    =>
    {

    const [value, setValue] = useState(1)

    const [name, setName] = useState("Alex")

    const increment = ()
    =>
    setValue(x
    =>
    x + 1)

    return {value}

    button>

    }

    1 "Alex"
    state storage
    0 1 2 3 4

    View Slide

  65. 55
    const App = ()
    =>
    {

    const [value, setValue] = useState(1)

    const [name, setName] = useState("Alex")

    const increment = ()
    =>
    setValue(x
    =>
    x + 1)

    return {value}

    button>

    }

    1 "Alex"
    state storage
    0 1 2 3 4

    View Slide

  66. 56
    const App = ()
    =>
    {

    const [value, setValue] = useState(1)

    const [name, setName] = useState("Alex")

    const increment = ()
    =>
    setValue(x
    =>
    x + 1)

    return {value}

    button>

    }

    2 "Alex"
    state storage
    0 1 2 3 4

    View Slide

  67. 57
    const App = ()
    =>
    {

    const [value, setValue] = useState(1)

    const [name, setName] = useState("Alex")

    const increment = ()
    =>
    setValue(x
    =>
    x + 1)

    return {value}

    button>

    }

    2 "Alex"
    state storage
    0 1 2 3 4

    View Slide

  68. 58
    специфика внутренней реализации хуков накладывает
    особые «ограничения» на разработчиков
    правила использования!
    хуки можно использовать только внутри компонентов


    определять хуки только в самом начале метода


    порядок нельзя менять (нельзя вызывать внутри if-else, циклов и т.д.)

    View Slide

  69. 59
    демо #1
    двоичный сумматор
    0 + 0 = 00

    0 + 1 = 01

    1 + 0 = 01

    1 + 1 = 10

    |
    |
    _ sum bit

    |

    carry bit
    будет полезно


    руководство по битовым операциям в JS

    ->
    Practical bit manipulation in JavaScript

    View Slide

  70. 04. хук useEffect

    View Slide

  71. 61
    import { useEffect } from "react"

    const App = ()
    =>
    {

    useEffect(()
    =>
    {

    /*
    effect body
    */


    }, [a, b, c])

    }

    ← массив зависимостей
    useEffect позволяет выполнять побочные эффекты в компоненте по условию

    View Slide

  72. 62
    как useEffect работает с зависимостями
    A B C D
    A A
    A A
    A B
    A B
    каждый рендер

    mount + update
    undefined

    []

    [x, y]

    mount
    mount + x или y поменялись

    View Slide

  73. 63
    для чего следует применять useEffect?
    для выполнения императивных эффектов: проиграть звук, перейти в
    полноэкранный режим, порисовать на …


    для подписки на внешний источник данных (вне Реакта): веб-сокеты,
    изменение размера окна, переход в оффлайн-режим …


    для расчёта state на основе props сверху

    View Slide

  74. 64
    демо #2
    обновляем заголовок страницы с помощью useEffect


    (без зависимостей)
    → https://developer.mozilla.org/en-US/docs/Web/API/Document/title
    document.title = "Hi!"

    View Slide

  75. 65
    демо #3
    подписка на Page Visibility


    (с пустым массивом зависимостей)
    → https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API
    document.visibilityState
    //
    "hidden" or "visible"

    View Slide

  76. 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}!`

    }

    View Slide

  77. 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

    View Slide

  78. 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 подписка на событие

    View Slide

  79. 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

    View Slide

  80. 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

    View Slide

  81. 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

    View Slide

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

    View Slide

  83. 67
    useEffect(()
    =>
    {

    document.title = `You clicked ${count} times`

    })

    View Slide

  84. useEffect(()
    =>
    {

    document.title = `You clicked ${count} times`

    }, [count])

    68
    с помощью массива зависимостей можно
    избежать вычислений «впустую»

    View Slide

  85. 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])

    }

    View Slide

  86. 05. хук useRef

    View Slide

  87. 71
    хук useRef — локальные переменные в

    функциональных компонентах (аналог this.*)
    const ref = useRef(null)

    if (!ref.current) ref.current = "alex"

    View Slide

  88. 71
    хук useRef — локальные переменные в

    функциональных компонентах (аналог this.*)
    const ref = useRef(null)

    if (!ref.current) ref.current = "alex"

    ref — это объект вида


    { current: null }

    View Slide

  89. 71
    хук useRef — локальные переменные в

    функциональных компонентах (аналог this.*)
    const ref = useRef(null)

    if (!ref.current) ref.current = "alex"

    ref — это объект вида


    { current: null }
    ref — локальная переменная,

    поэтому такое не сработает:


    ref = 41

    View Slide

  90. 72
    изменение значения useRef не приводит к перерисовке!
    userRef.current = "Alex"

    в отличие от useState

    View Slide

  91. 73
    через useRef можно сохранить ссылку на DOM-элемент
    используя свойство ref примитивных JSX элементов
    Реакт автоматически запишет в current ссылку на элемент
    function AudioPlayer() {

    const audioElement = useRef()

    const play = ()
    =>
    {

    audioElement.current.play()

    }

    return />


    }

    View Slide

  92. 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

    })

    View Slide

  93. 75
    useRef удобен для хранения вспомогательных объектов,
    которые нужно инициализировать только один раз
    мы могли бы создавать один confettiLauncher на компонент

    View Slide

  94. 06. хук useMemo

    View Slide

  95. 77
    хук useMemo — мемоизирует вычисления на
    основе зависимостей
    const data = useMemo(

    ()
    =>
    decrypt(message, key),

    [message, key]

    )

    алгоритм инвалидации такой же, как и в useEffect

    View Slide

  96. 78
    const carry = lhs & rhs

    const carry = useMemo(()
    =>
    lhs & rhs, [lhs, rhs])

    View Slide

  97. 07. композиция хуков

    View Slide

  98. 80
    const App = (props)
    =>
    {

    useEffect(()
    =>
    {

    document.title = props.title

    }, [props.title])

    }

    View Slide

  99. 81
    const useDocumentTitle = title
    =
    >
    {

    useEffect(()
    =>
    {

    document.title = title

    }, [title])

    }

    const App = (props)
    =>
    {

    useDocumentTitle(props.title)

    }

    наш собственный хук,


    название use*
    важнейшее свойство хуков: из них можно строить новые хуки


    (для переиспользования логики)

    View Slide

  100. 82
    демо #5
    хук useHashLocation
    → https://github.com/molefrog/wouter#customizing-the-location-hook
    http:
    //
    twitter.com/mlfrg#feed


    /mlfrg path

    #feed hash

    View Slide

  101. 83
    components
    композиция и переиспользование
    уровня представления (view)
    / >
    antd


    / >
    @react-pdf-viewer


    />
    react-colorful

    View Slide

  102. 83
    components
    композиция и переиспользование
    уровня представления (view)
    hooks
    композиция и переиспользование
    уровня состояния и логики
    useSpring() react-spring

    useDebounce() use-debounce

    useSound() use-sound
    / >
    antd


    / >
    @react-pdf-viewer


    />
    react-colorful

    View Slide

  103. 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


    Дайте волю хукам! Пишем миниатюрный роутер.

    View Slide

  104. Дайте волю хукам! Пишем миниатюрный роутер.

    View Slide