habrahabr

Изучаем хук useRef в React.js

  • среда, 26 августа 2020 г. в 00:31:34
https://habr.com/ru/post/516536/
  • Разработка веб-сайтов
  • JavaScript
  • Программирование
  • ReactJS




Доброго времени суток, друзья.

Представляю вашему вниманию перевод небольшой заметки про использование хука useRef в React.

Хук useState позволяет добавлять состояние в функциональные компоненты. Данный хук предоставляет возможность передавать значение, сохраняемое при повторном рендеринге страницы, а также интерфейс для обновления этого значения и принудительного запуска ререндеринга.

const [value, setValueAndRerender] = React.useState(
    'Начальное значение'
)

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

Но что если у нас нет необходимости в повторном рендеринге, но при этом нам нужно сохранять значение? В данном случае, нам нужна лишь половина useState:

function usePersistentValue(initialValue) {
    return React.useState({
        current: initialValue
    })[0]
}

useState возвращает массив, в котором первым элементом является сохраненное значение, а вторым — функция, запускающая повторный рендеринг.

Нам нужен только первый элемент, поэтому мы добавляем [0]. При запуске usePersistentValue мы получим объект, в свойстве которого будет содержаться сохраненное значение.

Рассмотрим пример.

Создадим простое приложение, в котором значение счетчика (count) будет увеличиваться на 1 каждую секунду, с кнопкой для остановки счетчика. Вот как может выглядеть реализация данного приложения:

function Counter() {
    const [count, setCount] = React.useState(0)

    let id

    const clear = () => clearInterval(id)

    React.useEffect(() => {
        id = setInterval(() => setCount(c => c + 1), 1000)

        return clear
    }, [])

    return (
        <div>
            <h1>{count}</h1>
            <button onClick={clear}>Стоп</button>
        </div>
    )
}

id создается внутри useEffect, однако нам нужно иметь доступ к этой переменной в обработчике для остановки счетчика. Поэтому мы объявляем id в основной области видимости и инициализируем ее при запуске эффекта.

Все хорошо, правда? К сожалению, нет. Дело в том, что значение id не сохраняется. При изменении состояния count React запускает ререндеринг и значение id сбрасывается до undefined.

Нам необходим способ сохранять значение id при ререндеринге. К счастью, для этого у нас имеется функция usePersistentValue. Попробуем ее использовать:

function usePersistentValue(initialValue) {
    return React.useState({
        current: initialValue
    })[0]
}

function Counter() {
    const [count, setCount] = React.useState(0)
    const id = usePersistentValue(null)

    const clear = () => clearInterval(id.current)

    React.useEffect(() => {
        id.current = setInterval(() => setCount(c => c + 1), 1000)

        return clear
    }, [])

    return (
        <div>
            <h1>{count}</h1>
            <button onClick={clear}>Стоп</button>
        </div>
    )
}

Выглядит не очень хорошо, но все работает. Значение id не сбрасывается при каждом рендеринге, поскольку оно содержится в useState, т.е. React сохраняет это значение.

Как вы, наверное, догадались, возможность сохранять значение без запуска повторного рендеринга является настолько востребованной, что React предоставляет для этого встроенный хук — useRef. По сути, это тоже самое, что наша функция usePersistentValue. Вот тот же код с использованием useRef:

function Counter() {
    const [count, setCount] = React.useState(0)
    const id = React.useRef(null)

    const clear = () => clearInterval(id.current)

    React.useEffect(() => {
        id.current = setInterval(() => setCount(c => c + 1), 1000)

        return clear
    }, [])

    return (
        <div>
            <h1>{count}</h1>
            <button onClick={clear}>Стоп</button>
        </div>
    )
}

useRef принимает начальное значение в качестве первого аргумента и возвращает объект со свойством current. Значение, которое присваивается этому свойству, сохраняется при ререндеринге.

Самым распространенным случаем использования useRef является получение доступа к узлам DOM. Если присвоить значение, возвращаемое useRef, какому-нибудь элементу, этот элемент станет значением свойства current. Такой прием используется для получения значений полей для ввода и установки фокуса:

function Form() {
    const nameRef = React.useRef()
    const emailRef = React.useRef()
    const passwordRef = React.useRef()

    const handleSubmit = ev => {
        ev.preventDefault()

        const name = nameRef.current.value
        const email = emailRef.current.value
        const password = passwordRef.current.value

        console.log(name, email, password)
    }

    return (
        <React.Fragment>
            <label>
                Имя:
                <input
                    type="text"
                    placeholder="name"
                    ref={nameRef}
                />
            </label>
            <label>
                Адрес электронной почты:
                <input
                    type="text"
                    placeholder="email"
                    ref={emailRef}
                />
            </label>
            <label>
                Пароль:
                <input
                    type="text"
                    placeholder="password"
                    ref={passwordRef}
                />
            </label>

            <hr />

            <button onClick={() => nameRef.current.focus()}>
                Установить фокус на поле для ввода имени
            </button>
            <button onClick={() => emailRef.current.focus()}>
                Установить фокус на поле для ввода адреса электронной почты
            </button>
            <button onClick={() => passwordRef.current.focus()}>
                Установить фокус на поле для ввода пароля
            </button>
        </React.Fragment>
    )
}

Таким образом, для добавления в компонент состояния, в котором сохраняется значение и запускается повторный рендеринг, следует использовать useState или useReducer. Для добавления в компонент состояния, в котором сохраняется значение, но не запускается повторный рендеринг, следует использовать useRef.

Благодарю за внимание.