Spec-Zone.ru › React

Выходные точки

Продвинутый

Некоторые ваши компоненты могут потребоваться для управления и синхронизации с системами, находящимися за пределами 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.

Нажмите Play/Pause несколько раз и посмотрите, как видеоплеер остаётся синхронизированным со значением свойства 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 предоставляет правило linter для проверки того, что вы правильно указали зависимости вашего эффекта. Если вы забудете указать roomId в списке зависимостей в приведённом выше примере, linter автоматически найдёт эту ошибку.

Готовы изучить эту тему?

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

Читать дальше

Отделение событий от эффектов

Разрабатывается

Этот раздел описывает экспериментальный API, который ещё не выпущен в стабильной версии React.

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

Весь код внутри эффектов реактивный. Он будет повторно выполняться, если какое-либо реактивное значение, которое он считывает, изменилось из-за перерендеринга. Например, этот эффект переподключится к чату, если изменятся либо 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 больше не заставляет ваш эффект переподключаться.

Готовы изучить эту тему?

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

Читать дальше

Удаление зависимостей эффектов

При написании эффекта linter проверит, что вы включили все реактивные значения (например, свойства и состояние), которые эффект считывает в список зависимостей эффекта. Это гарантирует, что ваш эффект остаётся синхронизированным с последними свойствами и состоянием вашего компонента. Необязательные зависимости могут привести к тому, что ваш эффект будет запускаться слишком часто или даже создаст бесконечный цикл. Способ удаления зависит от случая.

Например, этот эффект зависит от объекта 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.

Готовы изучить эту тему?

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

Читать далее

Что дальше?

Перейдите к Ссылкам на значения с помощью Refs, чтобы начать чтение этой главы по страницам!

© 2013–present Facebook Inc.
Licensed under the Creative Commons Attribution 4.0 International Public License.
https://react.dev/learn/escape-hatches

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API