Хук useRef

Последнее обновление: 01.04.2022

Хук 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 поля ввода:

хук useRef в React

Но это могут быть самые различные действия - получение и изменение свойств или вызов методов.

Однако только операциями с элементами 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 и useRef в React

Поскольку 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 по-прежнему сработает только один раз

state и ref в useEffect и useRef в React
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850