Выходы из системы
Некоторые из ваших компонентов могут потребоваться для управления и синхронизации с системами за пределами React. Например, вам может потребоваться сфокусировать поле ввода с помощью API браузера, воспроизвести и приостановить видеоплеер, реализованный без React, или подключиться и прослушивать сообщения с удаленного сервера. В этой главе вы узнаете о выходах из системы, которые позволяют вам «выйти за пределы» React и подключиться к внешним системам. Большая часть логики и потока данных вашего приложения не должна полагаться на эти функции.
В этой главе
- Как запомнить информацию без повторного рендеринга
- Как получить доступ к элементам DOM, управляемым React
- Как синхронизировать компоненты с внешними системами
- Как убрать необоснованные эффекты из ваших компонентов
- Как жизненный цикл эффекта отличается от жизненного цикла компонента
- Как предотвратить повторное срабатывание эффектов некоторыми значениями
- Как заставить ваш эффект выполняться реже
- Как использовать логику между компонентами
Ссылка на значения с помощью refs
Когда вы хотите, чтобы компонент «запоминал» некоторую информацию, но не хотите, чтобы эта информация вызывала новые рендеринги, вы можете использовать ref:
const ref = useRef(0); Как и состояние, refs сохраняются React между перерендерингами. Однако установка состояния вызывает перерендеринг компонента. Изменение ref этого не делает! Вы можете получить доступ к текущему значению этого ref через свойство ref.current.
import { useRef } from 'react'; export default function Counter() { let ref = useRef(0); function handleClick() { ref.current = ref.current + 1; alert('You clicked ' + ref.current + ' times!'); } return ( <button onClick={handleClick}> Click me! </button> ); }
Ref — это как тайный карман вашего компонента, который React не отслеживает. Например, вы можете использовать refs для хранения идентификаторов таймаутов, элементов DOM и других объектов, которые не влияют на выходные данные рендеринга компонента.
Готовы изучить эту тему?
Прочитайте Ссылка на значения с помощью Refs, чтобы узнать, как использовать refs для запоминания информации.
Читать далееРабота с DOM с помощью refs
React автоматически обновляет DOM, чтобы соответствовать вашим выходным данным рендеринга, поэтому вашим компонентам часто не нужно будет его изменять. Однако иногда вам может потребоваться доступ к элементам DOM, управляемым React, — например, для фокусировки узла, прокрутки к нему или измерения его размера и положения. В React нет встроенного способа сделать это, поэтому вам понадобится ref к узлу DOM. Например, нажав кнопку, вы сфокусируетесь на поле ввода с помощью ref:
import { useRef } from 'react'; export default function Form() { const inputRef = useRef(null); function handleClick() { inputRef.current.focus(); } return ( <> <input ref={inputRef} /> <button onClick={handleClick}> Focus the input </button> </> ); }
Готовы изучить эту тему?
Прочитайте Работа с DOM с помощью Refs, чтобы узнать, как получить доступ к элементам DOM, управляемым React.
Читать далееСинхронизация с эффектами
Некоторые компоненты нуждаются в синхронизации с внешними системами. Например, вы можете захотеть управлять компонентом, не являющимся React, на основе состояния React, установить соединение с сервером или отправить журнал аналитики, когда компонент появляется на экране. В отличие от обработчиков событий, которые позволяют обрабатывать определенные события, эффекты позволяют запускать некоторый код после рендеринга. Используйте их для синхронизации компонента с системой за пределами React.
Нажмите Воспроизвести/Приостановить несколько раз, чтобы увидеть, как видеоплеер остается синхронизированным со значением свойства isPlaying:
import { useState, useRef, useEffect } from 'react'; function VideoPlayer({ src, isPlaying }) { const ref = useRef(null); useEffect(() => { if (isPlaying) { ref.current.play(); } else { ref.current.pause(); } }, [isPlaying]); return <video ref={ref} src={src} loop playsInline />; } export default function App() { const [isPlaying, setIsPlaying] = useState(false); return ( <> <button onClick={() => setIsPlaying(!isPlaying)}> {isPlaying ? 'Pause' : 'Play'} </button> <VideoPlayer isPlaying={isPlaying} src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4" /> </> ); }
Многие эффекты также «очищают» за собой. Например, эффект, устанавливающий соединение с чат-сервером, должен возвращать функцию очистки, которая указывает React, как отключить ваш компонент от этого сервера:
import { useState, useEffect } from 'react'; import { createConnection } from './chat.js'; export default function ChatRoom() { useEffect(() => { const connection = createConnection(); connection.connect(); return () => connection.disconnect(); }, []); return <h1>Welcome to the chat!</h1>; }
Во время разработки React будет немедленно запускать и очищать ваш эффект один дополнительный раз. Вот почему вы видите "✅ Connecting..." напечатанным дважды. Это гарантирует, что вы не забудете реализовать функцию очистки.
Готовы изучить эту тему?
Прочитайте Синхронизация с эффектами, чтобы узнать, как синхронизировать компоненты с внешними системами.
Читать далееВам может не понадобиться эффект
Эффекты — это выход из парадигмы React. Они позволяют «выйти за пределы» React и синхронизировать ваши компоненты с какой-либо внешней системой. Если нет вовлеченной внешней системы (например, если вы хотите обновить состояние компонента при изменении некоторых свойств или состояния), вам не нужен эффект. Удаление необоснованных эффектов сделает ваш код более понятным, быстрее работающим и менее подверженным ошибкам.
Существует два распространенных случая, когда вам не нужны эффекты:
- Вам не нужны эффекты для преобразования данных для рендеринга.
- Вам не нужны эффекты для обработки пользовательских событий.
Например, вам не нужен эффект для корректировки состояния на основе другого состояния:
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
// 🔴 Avoid: redundant state and unnecessary Effect
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
// ...
} Вместо этого рассчитайте как можно больше во время рендеринга:
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
// ✅ Good: calculated during rendering
const fullName = firstName + ' ' + lastName;
// ...
} Однако эффекты необходимы для синхронизации с внешними системами.
Готовы изучить эту тему?
Прочитайте Вам может не понадобиться эффект, чтобы узнать, как убрать необоснованные эффекты.
Читать далееЖизненный цикл реактивных эффектов
Эффекты имеют другой жизненный цикл, чем компоненты. Компоненты могут монтироваться, обновляться или демонтироваться. Эффект может только две вещи: начать синхронизацию чего-либо и позже остановить её синхронизацию. Этот цикл может происходить несколько раз, если ваш эффект зависит от свойств и состояния, которые меняются со временем.
Этот эффект зависит от значения свойства roomId. Свойства — это реактивные значения, которые могут изменяться при перерендеринге. Обратите внимание, что эффект пересинхронизируется (и переподключается к серверу), если roomId изменяется:
import { useState, useEffect } from 'react'; import { createConnection } from './chat.js'; const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId }) { useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => connection.disconnect(); }, [roomId]); return <h1>Welcome to the {roomId} room!</h1>; } export default function App() { const [roomId, setRoomId] = useState('general'); return ( <> <label> Choose the chat room:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="general">general</option> <option value="travel">travel</option> <option value="music">music</option> </select> </label> <hr /> <ChatRoom roomId={roomId} /> </> ); }
React предоставляет правило проверки, чтобы убедиться, что вы правильно указали зависимости вашего эффекта. Если вы забудете указать roomId в списке зависимостей в приведенном выше примере, линтер автоматически обнаружит эту ошибку.
Готовы изучить эту тему?
Прочитайте Жизненный цикл реактивных событий, чтобы узнать, как жизненный цикл эффекта отличается от жизненного цикла компонента.
Читать далееОтделение событий от эффектов
В разработке
Этот раздел описывает экспериментальный API, который еще не выпущен в стабильной версии React.
Обработчики событий выполняют перерендеринг только при повторном выполнении того же взаимодействия. В отличие от обработчиков событий, эффекты пересинхронизируются, если какие-либо значения, которые они считывают, такие как свойства или состояние, отличаются от значений во время последнего рендеринга. Иногда вам нужен mix обоих поведений: эффект, который выполняется повторно в ответ на некоторые значения, но не на другие.
Весь код внутри эффектов реактивен. Он будет выполняться повторно, если какое-либо реактивное значение, которое он считывает, изменилось из-за перерендеринга. Например, этот эффект повторно подключится к чату, если изменились roomId или theme:
import { useState, useEffect } from 'react'; import { createConnection, sendMessage } from './chat.js'; import { showNotification } from './notifications.js'; const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId, theme }) { useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.on('connected', () => { showNotification('Connected!', theme); }); connection.connect(); return () => connection.disconnect(); }, [roomId, theme]); return <h1>Welcome to the {roomId} room!</h1> } export default function App() { const [roomId, setRoomId] = useState('general'); const [isDark, setIsDark] = useState(false); return ( <> <label> Choose the chat room:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="general">general</option> <option value="travel">travel</option> <option value="music">music</option> </select> </label> <label> <input type="checkbox" checked={isDark} onChange={e => setIsDark(e.target.checked)} /> Use dark theme </label> <hr /> <ChatRoom roomId={roomId} theme={isDark ? 'dark' : 'light'} /> </> ); }
Это не идеально. Вы хотите повторно подключиться к чату только в том случае, если изменилось roomId. Изменение theme не должно повторно подключать вас к чату! Вынесите код, считывающий theme, из вашего эффекта в событие эффекта:
import { useState, useEffect } from 'react'; import { experimental_useEffectEvent as useEffectEvent } from 'react'; import { createConnection, sendMessage } from './chat.js'; import { showNotification } from './notifications.js'; const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId, theme }) { const onConnected = useEffectEvent(() => { showNotification('Connected!', theme); }); useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.on('connected', () => { onConnected(); }); connection.connect(); return () => connection.disconnect(); }, [roomId]); return <h1>Welcome to the {roomId} room!</h1> } export default function App() { const [roomId, setRoomId] = useState('general'); const [isDark, setIsDark] = useState(false); return ( <> <label> Choose the chat room:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="general">general</option> <option value="travel">travel</option> <option value="music">music</option> </select> </label> <label> <input type="checkbox" checked={isDark} onChange={e => setIsDark(e.target.checked)} /> Use dark theme </label> <hr /> <ChatRoom roomId={roomId} theme={isDark ? 'dark' : 'light'} /> </> ); }
Код внутри событий эффектов не реактивен, поэтому изменение theme больше не заставляет ваш эффект повторно подключаться.
Готовы изучить эту тему?
Прочитайте Отделение событий от эффектов, чтобы узнать, как предотвратить повторное срабатывание эффектов некоторыми значениями.
Читать далееУдаление зависимостей эффекта
При написании эффекта линтер проверит, что вы включили каждое реактивное значение (например, свойства и состояние), которое эффект считывает, в список зависимостей эффекта. Это гарантирует, что эффект остается синхронизированным с последними свойствами и состоянием вашего компонента. Неоправданные зависимости могут привести к тому, что ваш эффект будет выполняться слишком часто или даже к созданию бесконечного цикла. Способ удаления зависит от случая.
Например, этот эффект зависит от объекта options, который пересоздаётся каждый раз, когда вы редактируете ввод:
import { useState, useEffect } from 'react'; import { createConnection } from './chat.js'; const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId }) { const [message, setMessage] = useState(''); const options = { serverUrl: serverUrl, roomId: roomId }; useEffect(() => { const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); }, [options]); return ( <> <h1>Welcome to the {roomId} room!</h1> <input value={message} onChange={e => setMessage(e.target.value)} /> </> ); } export default function App() { const [roomId, setRoomId] = useState('general'); return ( <> <label> Choose the chat room:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="general">general</option> <option value="travel">travel</option> <option value="music">music</option> </select> </label> <hr /> <ChatRoom roomId={roomId} /> </> ); }
Вы не хотите, чтобы чат переподключался каждый раз, когда вы начинаете печатать сообщение в этом чате. Чтобы исправить эту проблему, перемещение создания объекта options внутри эффекта, чтобы эффект зависел только от строки roomId:
import { useState, useEffect } from 'react'; import { createConnection } from './chat.js'; const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId }) { const [message, setMessage] = useState(''); useEffect(() => { const options = { serverUrl: serverUrl, roomId: roomId }; const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); }, [roomId]); return ( <> <h1>Welcome to the {roomId} room!</h1> <input value={message} onChange={e => setMessage(e.target.value)} /> </> ); } export default function App() { const [roomId, setRoomId] = useState('general'); return ( <> <label> Choose the chat room:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="general">general</option> <option value="travel">travel</option> <option value="music">music</option> </select> </label> <hr /> <ChatRoom roomId={roomId} /> </> ); }
Обратите внимание, что вы не начинали с редактирования списка зависимостей, чтобы удалить зависимость options. Это было бы неправильно. Вместо этого вы изменили окружающий код, чтобы зависимость стала излишней. Представьте себе список зависимостей как список всех реактивных значений, используемых кодом вашего эффекта. Вы не выбираете, что поместить в этот список. Список описывает ваш код. Чтобы изменить список зависимостей, измените код.
Готовы изучить эту тему?
Прочитайте Удаление зависимостей эффекта, чтобы узнать, как заставить ваш эффект выполняться реже.
Читать далееИспользование логики с пользовательскими хуками
React поставляется со встроенными хуками, такими как useState, useContext и useEffect. Иногда вам захочется, чтобы существовал хук для какой-то более специфической цели: например, для получения данных, для отслеживания того, подключен ли пользователь или для подключения к чат-комнате. Для этого вы можете создать собственные хуки для нужд вашего приложения.
В этом примере пользовательский хук usePointerPosition отслеживает положение курсора, а пользовательский хук useDelayedValue возвращает значение, «отстающее» от переданного значения на определённое количество миллисекунд. Перемещайте курсор над областью предварительного просмотра песочницы, чтобы увидеть перемещающуюся цепочку точек, следующую за курсором:
import { usePointerPosition } from './usePointerPosition.js'; import { useDelayedValue } from './useDelayedValue.js'; export default function Canvas() { const pos1 = usePointerPosition(); const pos2 = useDelayedValue(pos1, 100); const pos3 = useDelayedValue(pos2, 200); const pos4 = useDelayedValue(pos3, 100); const pos5 = useDelayedValue(pos4, 50); return ( <> <Dot position={pos1} opacity={1} /> <Dot position={pos2} opacity={0.8} /> <Dot position={pos3} opacity={0.6} /> <Dot position={pos4} opacity={0.4} /> <Dot position={pos5} opacity={0.2} /> </> ); } function Dot({ position, opacity }) { return ( <div style={{ position: 'absolute', backgroundColor: 'pink', borderRadius: '50%', opacity, transform: `translate(${position.x}px, ${position.y}px)`, pointerEvents: 'none', left: -20, top: -20, width: 40, height: 40, }} /> ); }
Вы можете создавать пользовательские хуки, комбинировать их вместе, передавать данные между ними и повторно использовать их между компонентами. По мере роста приложения вы будете писать меньше эффектов вручную, потому что сможете повторно использовать пользовательские хуки, которые вы уже написали. Также существует множество отличных пользовательских хуков, поддерживаемых сообществом React.
Готовы изучить эту тему?
Прочитайте Повторное использование логики с пользовательскими хуками, чтобы узнать, как делиться логикой между компонентами.
Читать далееЧто дальше?
Перейдите к Обращению к значениям с помощью ссылок, чтобы начать чтение этой главы по страницам!
© 2013–present Facebook Inc.
Licensed under the Creative Commons Attribution 4.0 International Public License.
https://18.react.dev/learn/escape-hatches