Spec-Zone.ru › React 18

Разделение событий от эффектов

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

Вы узнаете

  • Как выбрать между обработчиком события и эффектом
  • Почему эффекты являются реактивными, а обработчики событий — нет
  • Что делать, когда вы хотите, чтобы часть кода вашего эффекта не была реактивной
  • Что такое события эффектов и как извлечь их из ваших эффектов
  • Как читать последние свойства и состояние из эффектов с помощью событий эффектов

Выбор между обработчиками событий и эффектами

Сначала давайте вспомним разницу между обработчиками событий и эффектами.

Представьте, что вы реализуете компонент чата. Ваши требования выглядят так:

  1. Ваш компонент должен автоматически подключиться к выбранному чату.
  2. При нажатии кнопки «Отправить» он должен отправить сообщение в чат.

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

Обработчики событий выполняются в ответ на определенные взаимодействия

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

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');
  // ...
  function handleSendClick() {
    sendMessage(message);
  }
  // ...
  return (
    <>
      <input value={message} onChange={e => setMessage(e.target.value)} />
      <button onClick={handleSendClick}>Send</button>
    </>
  );
}

С помощью обработчика событий вы можете быть уверены, что sendMessage(message) будет выполняться только при нажатии пользователем кнопки.

Эффекты выполняются всякий раз, когда нужна синхронизация

Вспомните, что вам также нужно поддерживать подключение компонента к чату. Куда поместить этот код?

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

function ChatRoom({ roomId }) {
  // ...
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, [roomId]);
  // ...
}

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

import { useState, useEffect } from 'react';
import { createConnection, sendMessage } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  function handleSendClick() {
    sendMessage(message);
  }

  return (
    <>
      <h1>Welcome to the {roomId} room!</h1>
      <input value={message} onChange={e => setMessage(e.target.value)} />
      <button onClick={handleSendClick}>Send</button>
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  const [show, setShow] = 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>
      <button onClick={() => setShow(!show)}>
        {show ? 'Close chat' : 'Open chat'}
      </button>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId} />}
    </>
  );
}

Реактивные значения и реактивная логика

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

Есть более точный способ понять это.

Свойства, состояние и переменные, объявленные внутри тела вашего компонента, называются реактивными значениями. В этом примере serverUrl не является реактивным значением, но roomId и message — являются. Они участвуют в потоке данных рендеринга:

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  // ...
}

Реактивные значения, такие как эти, могут изменяться из-за повторного рендеринга. Например, пользователь может отредактировать message или выбрать другую roomId в раскрывающемся списке. Обработчики событий и эффекты реагируют на изменения по-разному:

  • Логика внутри обработчиков событий не является реактивной. Она не будет выполняться повторно, если пользователь не выполнит то же взаимодействие (например, щелчок) повторно. Обработчики событий могут читать реактивные значения, не «реагируя» на их изменения.
  • Логика внутри эффектов является реактивной. Если ваш эффект считывает реактивное значение, вам необходимо указать его в качестве зависимости. Затем, если повторный рендеринг вызывает изменение этого значения, React повторно выполнит логику вашего эффекта с новым значением.

Давайте пересмотрим предыдущий пример, чтобы проиллюстрировать эту разницу.

Логика внутри обработчиков событий не является реактивной

Посмотрите на эту строку кода. Должна ли эта логика быть реактивной или нет?

    // ...
    sendMessage(message);
    // ...

С точки зрения пользователя, изменение message не означает, что они хотят отправить сообщение. Это означает только, что пользователь печатает. Другими словами, логика, которая отправляет сообщение, не должна быть реактивной. Она не должна выполняться повторно только потому, что реактивное значение изменилось. Вот почему она принадлежит обработчику событий:

  function handleSendClick() {
    sendMessage(message);
  }

Обработчики событий не являются реактивными, поэтому sendMessage(message) будет выполняться только при нажатии пользователем кнопки «Отправить».

Логика внутри эффектов является реактивной

Теперь вернемся к этим строкам:

    // ...
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    // ...

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

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => {
      connection.disconnect()
    };
  }, [roomId]);

Эффекты являются реактивными, поэтому createConnection(serverUrl, roomId) и connection.connect() будут выполняться для каждого отдельного значения roomId. Ваш эффект поддерживает синхронизацию подключения к чату с текущей выбранной комнатой.

Извлечение нереактивной логики из эффектов

Дело становится сложнее, когда вы хотите смешать реактивную логику с нереактивной логикой.

Например, представьте, что вы хотите отобразить уведомление, когда пользователь подключается к чату. Вы считываете текущую тему (темная или светлая) из свойств, чтобы вы могли отобразить уведомление в правильном цвете:

function ChatRoom({ roomId, theme }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.on('connected', () => {
      showNotification('Connected!', theme);
    });
    connection.connect();
    // ...

Однако, theme является реактивным значением (оно может измениться в результате повторного рендеринга), и каждое считанное эффектом реактивное значение должно быть объявлено в качестве его зависимости. Теперь вам нужно указать theme в качестве зависимости вашего эффекта:

function ChatRoom({ roomId, theme }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.on('connected', () => {
      showNotification('Connected!', theme);
    });
    connection.connect();
    return () => {
      connection.disconnect()
    };
  }, [roomId, theme]); // ✅ All dependencies declared
  // ...

Поиграйте с этим примером и посмотрите, сможете ли вы обнаружить проблему с этим пользовательским опытом:

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 также является зависимостью, чат также переподключается каждый раз, когда вы переключаетесь между темной и светлой темой. Это не очень хорошо!

Другими словами, вы не хотите, чтобы эта строка была реактивной, даже если она находится внутри эффекта (который является реактивным):

      // ...
      showNotification('Connected!', theme);
      // ...

Вам нужен способ отделить эту нереактивную логику от реактивного эффекта вокруг нее.

Объявление события эффекта

В разработке

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

Используйте специальный хук, называемый useEffectEvent, чтобы выделить эту нереактивную логику из вашего эффекта:

import { useEffect, useEffectEvent } from 'react';

function ChatRoom({ roomId, theme }) {
  const onConnected = useEffectEvent(() => {
    showNotification('Connected!', theme);
  });
  // ...

Здесь, onConnected называется событием эффекта. Это часть вашей логики эффекта, но она ведет себя гораздо больше как обработчик событий. Логика внутри него не является реактивной и всегда «видит» последние значения ваших свойств и состояния.

Теперь вы можете вызвать событие эффекта onConnected внутри своего эффекта:

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]); // ✅ All dependencies declared
  // ...

Это решает проблему. Обратите внимание, что вам нужно было удалить onConnected из списка зависимостей вашего эффекта. События эффектов не являются реактивными и должны быть исключены из зависимостей.

Проверьте, что новое поведение работает так, как вы ожидаете:

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'}
      />
    </>
  );
}

Можно считать, что события эффектов очень похожи на обработчики событий. Основное различие заключается в том, что обработчики событий запускаются в ответ на взаимодействие пользователя, а события эффектов вызываются вами из эффектов. События эффектов позволяют «разрывать цепочку» между реактивностью эффектов и кодом, который не должен быть реактивным.

Чтение последних свойств и состояния с помощью событий эффектов

В разработке

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

События эффектов позволяют исправить многие шаблоны, где вы можете быть искушены подавить проверку зависимостей.

Например, у вас есть эффект для регистрации посещений страницы:

function Page() {
  useEffect(() => {
    logVisit();
  }, []);
  // ...
}

Позже вы добавляете несколько маршрутов на свой сайт. Теперь ваш компонент Page получает свойство url с текущим путем. Вы хотите передать url как часть вызова logVisit, но проверка зависимостей жалуется:

function Page({ url }) {
  useEffect(() => {
    logVisit(url);
  }, []); // 🔴 React Hook useEffect has a missing dependency: 'url'
  // ...
}

Подумайте о том, что вы хотите сделать с кодом. Вы хотите регистрировать отдельные посещения для разных URL-адресов, так как каждый URL-адрес представляет собой разную страницу. Другими словами, этот вызов logVisit должен быть реактивным относительно url. Вот почему в этом случае имеет смысл следовать проверке зависимостей и добавить url в качестве зависимости:

function Page({ url }) {
  useEffect(() => {
    logVisit(url);
  }, [url]); // ✅ All dependencies declared
  // ...
}

Теперь предположим, что вы хотите включить количество элементов в корзине вместе с каждым посещением страницы:

function Page({ url }) {
  const { items } = useContext(ShoppingCartContext);
  const numberOfItems = items.length;

  useEffect(() => {
    logVisit(url, numberOfItems);
  }, [url]); // 🔴 React Hook useEffect has a missing dependency: 'numberOfItems'
  // ...
}

Вы использовали numberOfItems внутри эффекта, поэтому проверка зависимостей попросит вас добавить его в качестве зависимости. Однако вы не хотите, чтобы вызов logVisit был реактивным относительно numberOfItems. Если пользователь добавляет что-то в корзину, и numberOfItems изменяется, это не означает, что пользователь посетил страницу снова. Другими словами, посещение страницы — это, в некотором смысле, «событие». Это происходит в определенный момент времени.

Разделите код на две части:

function Page({ url }) {
  const { items } = useContext(ShoppingCartContext);
  const numberOfItems = items.length;

  const onVisit = useEffectEvent(visitedUrl => {
    logVisit(visitedUrl, numberOfItems);
  });

  useEffect(() => {
    onVisit(url);
  }, [url]); // ✅ All dependencies declared
  // ...
}

Здесь onVisit — это событие эффекта. Код внутри него не является реактивным. Вот почему вы можете использовать numberOfItems (или любое другое реактивное значение!) без опасений, что это заставит окружающий код повторно выполняться при изменениях.

С другой стороны, сам Эффект остается реактивным. Код внутри Эффекта использует свойство url, поэтому Эффект будет повторно выполняться после каждого перерендеринга с различным url. Это, в свою очередь, вызовет событие Эффекта onVisit.

В результате вы будете вызывать logVisit для каждого изменения в url, и всегда читать последнее numberOfItems. Однако, если numberOfItems изменится самостоятельно, это не заставит ни один из кодов перезапуститься.

Примечание

Возможно, вы задаетесь вопросом, можно ли вызвать onVisit() без аргументов и прочитать url внутри него:

  const onVisit = useEffectEvent(() => {
    logVisit(url, numberOfItems);
  });

  useEffect(() => {
    onVisit();
  }, [url]);

Это сработает, но лучше явно передать это url в событие Эффекта. Передавая url в качестве аргумента своему событию Эффекта, вы говорите, что посещение страницы с другим url представляет собой отдельное «событие» с точки зрения пользователя. visitedUrl является частью «события», которое произошло:

  const onVisit = useEffectEvent(visitedUrl => {
    logVisit(visitedUrl, numberOfItems);
  });

  useEffect(() => {
    onVisit(url);
  }, [url]);

Поскольку ваше событие Эффекта явно «запрашивает» visitedUrl, теперь вы не можете случайно удалить url из зависимостей Эффекта. Если вы удалите зависимость url (что приведет к тому, что отдельные посещения страниц будут считаться одним), линтер предупредит вас об этом. Вы хотите, чтобы onVisit было реактивным по отношению к url, поэтому вместо того, чтобы читать url внутри (где это не будет реактивным), вы передаете его из своего Эффекта.

Это становится особенно важным, если внутри Эффекта есть асинхронная логика:

  const onVisit = useEffectEvent(visitedUrl => {
    logVisit(visitedUrl, numberOfItems);
  });

  useEffect(() => {
    setTimeout(() => {
      onVisit(url);
    }, 5000); // Delay logging visits
  }, [url]);

Здесь, url внутри onVisit соответствует последнему значению url (которое могло уже измениться), но visitedUrl соответствует значению url, которое изначально вызвало запуск этого Эффекта (и этого вызова onVisit).

Подробный анализ

Можно ли подавить линтер зависимостей?

В существующих кодовых базах иногда можно увидеть подавление правила линтера так:

function Page({ url }) {
  const { items } = useContext(ShoppingCartContext);
  const numberOfItems = items.length;

  useEffect(() => {
    logVisit(url, numberOfItems);
    // 🔴 Avoid suppressing the linter like this:
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [url]);
  // ...
}

После того, как useEffectEvent станет стабильной частью React, мы рекомендуем никогда не подавлять линтер.

Первый недостаток подавления правила заключается в том, что React больше не будет предупреждать вас, когда вашему Эффекту нужно «реагировать» на новую реактивную зависимость, которую вы ввели в свой код. В предыдущем примере вы добавили url в зависимости потому, что React напомнил вам об этом. Вы больше не будете получать такие напоминания для будущих изменений в этом Эффекте, если отключите линтер. Это приводит к ошибкам.

Вот пример запутанной ошибки, вызванной подавлением линтера. В этом примере функция handleMove должна читать текущее значение переменной состояния canMove для того, чтобы решить, должна ли точка следовать за курсором. Однако, canMove всегда равно true внутри handleMove.

Вы видите, в чём проблема?

import { useState, useEffect } from 'react';

export default function App() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [canMove, setCanMove] = useState(true);

  function handleMove(e) {
    if (canMove) {
      setPosition({ x: e.clientX, y: e.clientY });
    }
  }

  useEffect(() => {
    window.addEventListener('pointermove', handleMove);
    return () => window.removeEventListener('pointermove', handleMove);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <>
      <label>
        <input type="checkbox"
          checked={canMove}
          onChange={e => setCanMove(e.target.checked)}
        />
        The dot is allowed to move
      </label>
      <hr />
      <div style={{
        position: 'absolute',
        backgroundColor: 'pink',
        borderRadius: '50%',
        opacity: 0.6,
        transform: `translate(${position.x}px, ${position.y}px)`,
        pointerEvents: 'none',
        left: -20,
        top: -20,
        width: 40,
        height: 40,
      }} />
    </>
  );
}

Проблема с этим кодом заключается в подавлении линтера зависимостей. Если вы удалите подавление, вы увидите, что этот Эффект должен зависеть от функции handleMove. Это имеет смысл: handleMove объявлена ​​внутри тела компонента, что делает её реактивным значением. Каждое реактивное значение должно быть указано в качестве зависимости, иначе оно может со временем стать устаревшим!

Автор исходного кода «соврал» React, сказав, что Эффект не зависит ([]) ни от каких реактивных значений. Вот почему React не пересинхронизировал Эффект после того, как canMove изменился (и handleMove вместе с ним). Поскольку React не пересинхронизировал Эффект, handleMove прикреплённый в качестве слушателя - это функция handleMove созданная во время первоначального рендеринга. Во время начального рендеринга canMove было true, поэтому handleMove из первоначального рендеринга навсегда увидит это значение.

Если вы никогда не подавляете линтер, вы никогда не столкнётесь с проблемами с устаревшими значениями.

С useEffectEvent, нет необходимости «обманывать» линтер, и код работает так, как вы ожидаете:

import { useState, useEffect } from 'react';
import { experimental_useEffectEvent as useEffectEvent } from 'react';

export default function App() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [canMove, setCanMove] = useState(true);

  const onMove = useEffectEvent(e => {
    if (canMove) {
      setPosition({ x: e.clientX, y: e.clientY });
    }
  });

  useEffect(() => {
    window.addEventListener('pointermove', onMove);
    return () => window.removeEventListener('pointermove', onMove);
  }, []);

  return (
    <>
      <label>
        <input type="checkbox"
          checked={canMove}
          onChange={e => setCanMove(e.target.checked)}
        />
        The dot is allowed to move
      </label>
      <hr />
      <div style={{
        position: 'absolute',
        backgroundColor: 'pink',
        borderRadius: '50%',
        opacity: 0.6,
        transform: `translate(${position.x}px, ${position.y}px)`,
        pointerEvents: 'none',
        left: -20,
        top: -20,
        width: 40,
        height: 40,
      }} />
    </>
  );
}

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

Прочитайте Удаление зависимостей эффекта для других правильных альтернатив подавлению линтера.

Ограничения событий эффектов

В разработке

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

События эффектов очень ограничены в плане использования:

  • Вызывайте их только внутри эффектов.
  • Никогда не передавайте их другим компонентам или хукам.

Например, не объявляйте и не передавайте событие эффекта так:

function Timer() {
  const [count, setCount] = useState(0);

  const onTick = useEffectEvent(() => {
    setCount(count + 1);
  });

  useTimer(onTick, 1000); // 🔴 Avoid: Passing Effect Events

  return <h1>{count}</h1>
}

function useTimer(callback, delay) {
  useEffect(() => {
    const id = setInterval(() => {
      callback();
    }, delay);
    return () => {
      clearInterval(id);
    };
  }, [delay, callback]); // Need to specify "callback" in dependencies
}

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

function Timer() {
  const [count, setCount] = useState(0);
  useTimer(() => {
    setCount(count + 1);
  }, 1000);
  return <h1>{count}</h1>
}

function useTimer(callback, delay) {
  const onTick = useEffectEvent(() => {
    callback();
  });

  useEffect(() => {
    const id = setInterval(() => {
      onTick(); // ✅ Good: Only called locally inside an Effect
    }, delay);
    return () => {
      clearInterval(id);
    };
  }, [delay]); // No need to specify "onTick" (an Effect Event) as a dependency
}

События эффектов - это нереактивные «части» вашего кода эффекта. Они должны находиться рядом с использующим их эффектом.

Резюме

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

Попробуйте выполнить некоторые задачи

Задача 1 из 4:
Исправить переменную, которая не обновляется

Этот Timer компонент сохраняет переменную состояния count, которая увеличивается каждую секунду. Значение, на которое она увеличивается, хранится в переменной состояния increment. Вы можете контролировать переменную increment с помощью кнопок плюс и минус.

Однако, сколько бы раз вы ни нажимали кнопку плюс, счетчик по-прежнему увеличивается на единицу каждую секунду. В чём проблема с этим кодом? Почему increment всегда равно 1 внутри кода эффекта? Найдите ошибку и исправьте её.

import { useState, useEffect } from 'react';

export default function Timer() {
  const [count, setCount] = useState(0);
  const [increment, setIncrement] = useState(1);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + increment);
    }, 1000);
    return () => {
      clearInterval(id);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <>
      <h1>
        Counter: {count}
        <button onClick={() => setCount(0)}>Reset</button>
      </h1>
      <hr />
      <p>
        Every second, increment by:
        <button disabled={increment === 0} onClick={() => {
          setIncrement(i => i - 1);
        }}>–</button>
        <b>{increment}</b>
        <button onClick={() => {
          setIncrement(i => i + 1);
        }}>+</button>
      </p>
    </>
  );
}

© 2013–present Facebook Inc.
Licensed under the Creative Commons Attribution 4.0 International Public License.
https://18.react.dev/learn/separating-events-from-effects

Spec-Zone.ru

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