С помощью атрибута ref React позволяет напрямую ссылаться на любой элемент веб-страницы, который прикреплен к определенной ссылке. Для создания подобной ссылки для функциональных компонентов можно применять хук useRef(), а для компонентов-классов - специальную функцию createRef().
Хук useRef позволяет сохранить некоторый объект, который можно можно изменять и который хранится в течение всей жизни компонента.
В качестве параметра функция useRef() принимает начальное значение хранимого объекта. А возвращаемое значение - ссылка-объект, из свойства current которого можно получить хранимое значение.
const refUser = React.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 src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel" data-type="module">
import React from "https://esm.sh/react@19?dev";
import ReactDOM from "https://esm.sh/react-dom@19/client?dev";
function UserForm() {
const nameField = React.useRef(null);
const send = () => {
// свойство current указывает на элемент input
const inputElement = nameField.current;
console.log("Имя: " + inputElement.value);
};
return (
<div>
<input 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 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 src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel" data-type="module">
import React from "https://esm.sh/react@19?dev";
import ReactDOM from "https://esm.sh/react-dom@19/client?dev";
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 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 src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel" data-type="module">
import React from "https://esm.sh/react@19?dev";
import ReactDOM from "https://esm.sh/react-dom@19/client?dev";
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 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 по-прежнему сработает только один раз
В классах-компонентах мы естественно не можем использовать хук useRef(), однако для компонентов-классов React предоставляет другую похожую возможность -
функцию React.createRef(). Эта функция, как и хук useRef(), позволяет создать ссылку, которую затем с помощью атрибута ref можно прикрепить
к определенному элементу разметки. Рассмотрим небольшой пример:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>METANIT.COM</title>
</head>
<body>
<div id="app"></div>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel" data-type="module">
import React from "https://esm.sh/react@19?dev";
import ReactDOM from "https://esm.sh/react-dom@19/client?dev";
class UserForm extends React.Component {
constructor(props) {
super(props);
this.nameField = React.createRef();
}
send = ()=>{
// свойство current указывает на элемент input
const inputElement = this.nameField.current;
console.log("Имя: ", inputElement.value);
}
render() {
return <div>
<input defaultValue="Tom" ref={this.nameField} />
<button onClick={this.send}>Отправить</button>
</div>;
}
}
ReactDOM.createRoot(
document.getElementById("app")
)
.render(
<UserForm />
);
</script>
</body>
</html>
Для создания ссылок ref применяется функция React.createRef(). В данном случае это происходит в конструкторе:
this.nameField = React.createRef();
Затем созданную ссылку можно прикрепить к какому-нибудь элементу на html-странице. Для этого применяется применяется атрибут ref,
которому в фигурных скобках передается ссылка.
<input ref={this.nameField} />
Далее в коде мы сможем ссылаться на этот элемент, в том числе получать его значения, с помощью выражения this.nameField.current.
Единственный минус в этом случае, то что мы не можем установить значение по умолчанию с помощью стандартного атрибута value: при использовании атрибута value опять же придется задавать обработчик события change, как в прошлой теме. Альтернативой данному подходу служит применение специального атрибута defaultValue, который задает для поля ввода значение по умолчанию:
render() {
return <div>
<input defaultValue="Tom" ref={this.nameField} />
<button onClick={this.send}>Отправить</button>
</div>;
}