Slide 1

Slide 1 text

Алексей Тактаров · @mlfrg React Hooks: интерактивность в функциональных компонентах

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

5 Алексей Тактаров / @mlfrg 2014 прикладная математика и информатика мехмата ЮФУ ( McS ) 2009 – 2013 разрабатывал многопоточные, сетевые и системные приложения на Си/Си++ *люблю оптимизировать код и бороться за каждый байт

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

13 resume.io: запуск запустили MVP за месяц делали все подряд: рисовали, программировали, писали тексты выбрали React для фронтенда 
 (но лишь через год после запуска! )

Slide 15

Slide 15 text

14 resume.io сейчас 16M резюме, 25K + регистраций/день remote-first команда: Россия, Нидерланды, США Rails-монолит, React-приложение и 
 4 фронтенд-микросервиса

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

→ https://github.com/molefrog/wouter wouter микро-роутер, всего ~ 1400 байт написан полностью на React Hooks 3,500 + звездочек на GH

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

28 const Company = props = >
{props.name} div> const Company = (props) => { return
{props.name} div> } function Company(props) { return
{props.name} div> } функциональные компоненты могут быть определены как обычные функции, так и как анонимные arrow functions

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

30 class Clock extends React.Component { constructor(props) { super(props) this.state = { showWarning: false } } handleClick() { this.setState({ showWarning: true }) } render() { /* .. . * / } } ← инициализация ← обновление

Slide 35

Slide 35 text

31 за state отвечает сам компонент за props — компонент сверху изменение обоих приводит к перерисовке (rerender)

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

33

Slide 38

Slide 38 text

33 “change” event

Slide 39

Slide 39 text

33 “change” event setState({ name: “Ale” })

Slide 40

Slide 40 text

34

Slide 41

Slide 41 text

35

Slide 42

Slide 42 text

36

Slide 43

Slide 43 text

37 const ResumeEditor = props => { … } хуки дают позволяют функциональным компонентам 
 иметь побочные эффекты state, lifecycle-методы и др.

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

39 появились в React 16.8.0 (февраль 2019 ) результат многих лет разработки самого Реакта и 
 опыта реальных приложений React Hooks

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

03. хук useState

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

45 import { useState } from "react" const [user, setUser] = useState("Alex") → https://reactjs.org/docs/hooks-reference.html#usestate деструктуризация массива (array destructuring)

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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") }

Slide 57

Slide 57 text

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") } инициализация

Slide 58

Slide 58 text

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") } значение

Slide 59

Slide 59 text

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") } сеттер

Slide 60

Slide 60 text

50 import { useState } from "react" const App = () => { const [user, setUser] = useState("Alex") setUser("Menno") return `Hi, ${user}!` } ошибка: бесконечный цикл ререндера! 💥

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

59 демо #1 двоичный сумматор 0 + 0 = 00 0 + 1 = 01 1 + 0 = 01 1 + 1 = 10 | | _ sum bit 
 | 
 carry bit будет полезно 
 
 руководство по битовым операциям в JS 
 -> Practical bit manipulation in JavaScript

Slide 70

Slide 70 text

04. хук useEffect

Slide 71

Slide 71 text

61 import { useEffect } from "react" const App = () => { useEffect(() => { /* effect body */ }, [a, b, c]) } ← массив зависимостей useEffect позволяет выполнять побочные эффекты в компоненте по условию

Slide 72

Slide 72 text

62 как useEffect работает с зависимостями A B C D A A A A A B A B каждый рендер 
 mount + update undefined [] [x, y] mount mount + x или y поменялись

Slide 73

Slide 73 text

63 для чего следует применять useEffect? для выполнения императивных эффектов: проиграть звук, перейти в полноэкранный режим, порисовать на … для подписки на внешний источник данных (вне Реакта): веб-сокеты, изменение размера окна, переход в оффлайн-режим … для расчёта state на основе props сверху

Slide 74

Slide 74 text

64 демо #2 обновляем заголовок страницы с помощью useEffect (без зависимостей) → https://developer.mozilla.org/en-US/docs/Web/API/Document/title document.title = "Hi!"

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

67 useEffect(() => { document.title = `You clicked ${count} times` })

Slide 84

Slide 84 text

useEffect(() => { document.title = `You clicked ${count} times` }, [count]) 68 с помощью массива зависимостей можно избежать вычислений «впустую»

Slide 85

Slide 85 text

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]) }

Slide 86

Slide 86 text

05. хук useRef

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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 })

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

06. хук useMemo

Slide 95

Slide 95 text

77 хук useMemo — мемоизирует вычисления на основе зависимостей const data = useMemo( () => decrypt(message, key), [message, key] ) алгоритм инвалидации такой же, как и в useEffect

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

80 const App = (props) => { useEffect(() => { document.title = props.title }, [props.title]) }

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

83 components композиция и переиспользование уровня представления (view) antd 
 
 @react-pdf-viewer 
 react-colorful

Slide 102

Slide 102 text

83 components композиция и переиспользование уровня представления (view) hooks композиция и переиспользование уровня состояния и логики useSpring() react-spring useDebounce() use-debounce useSound() use-sound antd 
 
 @react-pdf-viewer 
 react-colorful

Slide 103

Slide 103 text

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 Дайте волю хукам! Пишем миниатюрный роутер.

Slide 104

Slide 104 text

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