Как предотвратить двойной запуск React useEffect при разработке в режиме StrictMode с внешней системой, не имеющей метода очистки?

Рассмотрим следующий пример:

pages/index.js

Здесь complexExternalLibraryFunctionWithoutCleanup, как следует из названия, представляет внешнюю библиотеку JavaScript, которая действует на заданный элемент и не имеет метода очистки.

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

В режиме разработки + StrictMode я заметил, что после нажатия кнопки Нажмите меня после ввода чего-либо! обратный вызов регистрируется дважды.

Однако в режиме производства это работает так, как и ожидалось.

Как правильно предотвратить эту проблему в режиме разработки? Это:

К сожалению, мне удалось воспроизвести проблему только на Next.js, а не на чистом примере React. Должно быть, я что-то упускаю. Остальные файлы Next.js для воспроизведения:

package.json

.eslintrc

next.config.js

а затем для режима разработки:

и режима производства:

Моя неудачная попытка воспроизведения на чистом React:

но по какой-то причине это не выявляет проблему.

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

import { useState, useRef, useEffect } from 'react'

function complexExternalLibraryFunctionWithoutCleanup(elem, cb) {
  elem.addEventListener('click', () => {
    cb()
  })
}

export default function IndexPage() {
  const [text, setText] = useState('')
  const [myint, setMyint] = useState(0)
  const ref = useRef(null)
  function incIt() {
    setMyint(i => i+1)
  }
  useEffect(() => {
    if (ref.current) {
      complexExternalLibraryFunctionWithoutCleanup(ref.current, incIt)
    }
  }, [])
  return <div>
    <div><input value={text} onChange={e => setText(e.target.value)} placeholder='Type here' /></div>
    <div>You typed: {text}</div>
    <div><button ref={ref}>Click me after typing something! If unfixed, it will increment twice!</button></div>
    <div><button onClick={incIt}>Click me to increment!</button></div>
    <div>{myint}</div>
  </div>
}
{
  "private": true,
  "scripts": {
    "dev": "next dev --turbo",
    "build": "next build",
    "start": "next start",
    "lint": "eslint ."
  },
  "dependencies": {
    "next": "14.2.5",
    "react": "18.2.0",
    "react-dom": "18.2.0"
  },
  "devDependencies": {
    "eslint": "7.24.0",
    "eslint-config-next": "14.2.5"
  }
}
{
  "extends": "next",
  "root": true
}
module.exports = {
  reactStrictMode: true,
}
npm install
npm run dev
npm run build
npm run start
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/[email protected]/babel.min.js"></script>
</head>
<body>
<p><a href="https://cirosantilli.com/_file/react/ref-twice.html">https://cirosantilli.com/_file/react/ref-twice.html</a></p>
<div id="root"></div>
<script type="text/babel">
const { StrictMode, useState, useEffect, useRef } = React

function complexExternalLibraryFunctionWithoutCleanup(elem, cb) {
  elem.addEventListener('click', () => {
    cb()
  })
}

function Main(props) {
  const [text, setText] = useState('')
  const [myint, setMyint] = useState(0)
  const ref = useRef(null)
  function incIt() {
    setMyint(i => i+1)
  }
  useEffect(() => {
    if (ref.current) {
      complexExternalLibraryFunctionWithoutCleanup(ref.current, incIt)
    }
  }, [])
  return <div>
    <div><input value={text} onChange={e => setText(e.target.value)} placeholder='Type here' /></div>
    <div>You typed: {text}</div>
    <div><button ref={ref}>Click me after typing something! If unfixed, it will increment twice!</button></div>
    <div><button onClick={incIt}>Click me to increment!</button></div>
    <div>{myint}</div>
  </div>
}
ReactDOM.createRoot(document.getElementById('root')).render(<StrictMode><Main /></StrictMode>)
</script>
</body>
</html>
Евгений
Вопрос задан17 июля 2024 г.

1 Ответ

2
Савватий
Ответ получен10 сентября 2024 г.

Ваш ответ

Загрузить файл.