Хук useRef позволяет сохранить некоторый объект, который можно можно изменять и который хранится в течение всей жизни компонента.
В качестве параметра функция useRef() принимает начальное значение хранимого объекта. А возвращаемое значение - ссылка-объект, из свойства current которого можно получить хранимое значение.
const refUser = useRef("Tom"); console.log(refUser.current); // Tom
Расспространенным примером применения useRef является хранение ссылки на html-элементы внутри компонента:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>METANIT.COM</title> </head> <body> <div id="app"></div> <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <script type="text/babel"> function UserForm() { const nameField = React.useRef(null); const send = () => { // свойство current указывает на элемент input const inputElement = nameField.current; console.log("Имя: " + inputElement.value); }; return ( <div> <input type="text" ref={nameField} /> <button onClick={send}>Отправить</button> </div> ); } ReactDOM.createRoot( document.getElementById("app") ) .render( <UserForm /> ); </script> </body> </html>
Здесь в компоненте сначала создается ссылка ref:
const nameField = React.useRef(null);
В данном случае нам начальное значение не важно, поэтому в useRef
передается значение null
.
Однако в html-коде компонента определено текствое поле ввода:
<input type="text" ref={nameField} />
С помощью атрибута ref
устанавливаем привязку этого поля к ссылке nameField
. То есть через свойство
nameField.current
мы сможем получить объект, который представляет это поле ввода <input>
.
const inputElement = nameField.current; console.log("Имя: " + inputElement.value);
Получив объект, который представляет поле ввода, мы сможем выполнять с ним необходимые операции. В данном случае имитируется отправка введенного значения -
ввод на консоль значения свойства value
поля ввода:
Но это могут быть самые различные действия - получение и изменение свойств или вызов методов.
Однако только операциями с элементами html применение useRef
не ограничивается. В реальности useRef
может
хранить любой объект, и это может быть полезно в различных ситуациях.
Например, рассмотрим ситуацию, когда вначале компонент загружает состояние из LocalStorage, а после окончания работы с компонентом при завершении его жизненного
цикла он сохраняет состояние обратно в LocalStorage. На первый взгляд мы можем обойтись одним хуком useEffect
:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>METANIT.COM</title> </head> <body> <div id="app"></div> <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <script type="text/babel"> const root = ReactDOM.createRoot( document.getElementById("app") ); function UserForm() { const [name, setName] = React.useState("Tom"); React.useEffect(() => { // извлекаем данные из localStorage const userName = localStorage.getItem("userName"); // если в localStorage есть такой объект if(userName!==null) { setName(userName); console.log("Got!"); } // сохраняем данные в localStorage return()=>{ console.log(name); localStorage.setItem("userName", name); console.log("Saved!"); } }, []); // эффект срабатывает только один раз - при самом первом рендеринге const changeName = (event) => setName(event.target.value); const unmount =() => root.unmount(); return ( <div> <h3>Имя: {name}</h3> <div> <p>Имя: <input type="text" value={name} onChange={changeName} /></p> <button onClick={unmount}>Unmount</button> </div> </div> ); } root.render( <UserForm /> ); </script> </body> </html>
Вначале в компоненте определяет начальное состояние в виде переменной name:
const [name, setName] = React.useState("Tom");
Далее в хуке React.useEffect()
загружаем данные из LocalStorage:
const userName = localStorage.getItem("userName");
И чтобы сохранять данные, оператору return передается функция сохранения данных:
return ()=>{ console.log(name); localStorage.setItem("userName", name); console.log("Saved!"); }
Поскольку нам нужно, чтобы эффект срабатывал только один раз - извлечение данных происходило при загрузке компонента, а сохранение данных при удалении компонента в конце его работы, в хук useEffect передаются пустые скобки:
React.useEffect(() => { // .................... }, []); // эффект вызывается только один раз
Для имитации удаления компонента и завершения его жизненного цикла в нем предумотрена кнопка, по нажатию на которую мы ожидаем, что произойдет сохранение значения переменной name в localStorage. Однако поведение программы будет несколько иное:
Поскольку useEffect срабатывает в данном случае один раз, то соответственно он берет значение перемеенной name только один раз и никак не отслеживает ее изменения. Мы, конечно, могли бы передать в качестве параметра эту переменную name:
React.useEffect(() => { // .................... }, [name]); // эффект вызывается при каждом обновлении name
Но тогда бы useEffect вызывался при каждом изменении переменной name.
Теперь применим хук useRef:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>METANIT.COM</title> </head> <body> <div id="app"></div> <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <script type="text/babel"> const root = ReactDOM.createRoot( document.getElementById("app") ); function UserForm() { const [name, setName] = React.useState("Tom"); const nameRef = React.useRef(name); React.useEffect(() => { nameRef.current = name; }, [name]); React.useEffect(() => { // извлекаем данные из localStorage const userName = localStorage.getItem("userName"); // если в localStorage есть такой объект if(userName!==null) { setName(userName); console.log("Got!"); } // сохраняем данные в localStorage return()=>{ console.log(nameRef.current); localStorage.setItem("userName", nameRef.current); console.log("Saved!"); } }, []); // эффект срабатывает только один раз - при самом первом рендеринге const changeName = (event) => setName(event.target.value); const unmount =() => root.unmount(); return ( <div> <h3>Имя: {name}</h3> <div> <p>Имя: <input type="text" value={name} onChange={changeName} /></p> <button onClick={unmount}>Unmount</button> </div> </div> ); } root.render( <UserForm /> ); </script> </body> </html>
Здесь вместе с состоянием компонента определяем ссылку nameRef:
const nameRef = React.useRef(name);
Ее начальное значение - это значение переменной name. И при каждом изменении переменной name соответственно меняем и значение в ссылке
nameRef. Для этого определяем эффект с помощью хука useEffect
:
React.useEffect(() => { nameRef.current = name; }, [name]);
При этом данный эффект зависит от name, то есть срабатывает при любых изменениях значения name.
Основной хук useEffect
, который сохраняет данные в LocalStorage, по прежнему запускается один раз - при первом рендеринге.
Однако теперь мы сохраняем не значение переменной name, а значение в ссылке nameRef:
// сохраняем данные в localStorage return()=>{ console.log(nameRef.current); localStorage.setItem("userName", nameRef.current); console.log("Saved!"); }
В отличие от переменной состояния name в useEffect, значение по ссылке nameRef будет изменяться, несмотря на то что, useEffect по-прежнему сработает только один раз