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

В приведенном ниже коде я запускаю повторный рендеринг App каждые 100 мс, устанавливая состояние, а React выводит «перерендеринг» в консоли каждые 100 мс.

Почему Child перерисовывается при каждом коммите? Разве я не «поднимаю контент» здесь?

import { useState, useEffect } from 'react';

const App = () => {
  const [now, setNow] = useState()

  // Start a timer
  useEffect(() => {
    const interval = setInterval(() =>
      setNow(Date.now()), 100)
    return () => clearInterval(interval)
  }, [])

  return (
    <div className="App">
      <Parent>
        <Child />
      </Parent>
    </div>
  )
}
export default App

const Parent = ({ children }) => {
  return (
    <div id='parent'>
      {children}
    </div>
  )
}

const Child = () => {
  console.log('rendered')
  return (
    <p>whatever</p>
  )
}
const { useEffect, useState } = React;

const Child = () => {
  console.log('rendered')
  return (
    <p>whatever</p>
  )
}

const Parent = ({ children }) => {
  return (
    <div id='parent'>
      {children}
    </div>
  )
}

const App = () => {
  const [now, setNow] = useState()

  // Start a timer
  useEffect(() => {
    const interval = setInterval(() =>
      setNow(Date.now()), 100)
    return () => clearInterval(interval)
  }, [])

  return (
    <div className="App">
      <Parent>
        <Child />
      </Parent>
    </div>
  )
}

ReactDOM
  .createRoot(document.getElementById("root"))
  .render(<App />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
2
Cirrocumulus 30 Май 2023 в 20:19

2 ответа

Вы можете запомнить часть Parent-Child внутри App. Поскольку состояние App обновляется каждые 100 мс, оно будет повторно отображаться каждые 100 мс сверху вниз. Если у вас есть элементы, которым не требуется значение now в качестве реквизита, вам нужно будет использовать мемоизацию.

Примечание. Я также переместил логику вашего таймера в хук.

const { useEffect, useMemo, useState } = React;

const Child = () => {
  console.log('rendered') // only logged one time
  return (
    <p>whatever</p>
  )
}

const Parent = ({ children }) => {
  return (
    <div>
      {children}
    </div>
  )
}

const useTime = (updateRateMs) => {
  const [intervalId, setIntervalId] = useState()
  const [now, setNow] = useState(Date.now())
  
  useEffect(() => {
    setIntervalId(setInterval(() => setNow(Date.now()), updateRateMs))
  }, [])
  
  useEffect(() => {
    return () => {
      clearTimeout(intervalId)
    }
  }, [intervalId])
  
  return { now }
}

const App = () => {
  const { now } = useTime(100); // Update now every 100ms

  const memoizedChild = useMemo(() => (
    <Parent>
      <Child />
    </Parent>
  ), [])

  return (
    <div className="App">
      {memoizedChild}
      <div>Current Time: {new Date(now).toLocaleString()}</div>
    </div>
  )
}

ReactDOM
  .createRoot(document.getElementById("root"))
  .render(<App />)
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
0
Mr. Polywhirl 30 Май 2023 в 21:12

Это происходит потому, что вы создаете новый компонент <Child /> каждый раз, когда обновляется состояние в компоненте <App/>. Таким образом, утверждение о том, что «если React все еще имеет ту же дочернюю поддержку, которую он получил от приложения в прошлый раз, React не посещает это поддерево», больше не соответствует действительности. Если вы переместите свой таймер из компонента <App/> в компонент <Parent/>, вы увидите в приведенном ниже фрагменте, что <Child/> больше не будет повторно отображаться, поскольку он создается только один раз в <App/> и передается как детская опора.

const { useEffect, useState } = React;

const Child = () => {
  console.log('rendered')
  return (
    <p>whatever</p>
  )
}

const Parent = ({ children }) => {
  const [now, setNow] = useState()

  // Start a timer
  useEffect(() => {
    const interval = setInterval(() =>
      setNow(Date.now()), 100)
    return () => clearInterval(interval)
  }, [])

  return (
    <div id='parent'>
      {children}
    </div>
  )
}

const App = () => {
  return (
    <div className="App">
      <Parent>
        <Child />
      </Parent>
    </div>
  )
}

ReactDOM
  .createRoot(document.getElementById("root"))
  .render(<App />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
0
Usaid 30 Май 2023 в 21:44