搜尋

首頁  >  問答  >  主體

React子元件無法感知來自useEffect內部非同步函數的狀態變化

<p>我試圖傳遞一個在非同步函數內部產生的狀態,該函數本身位於 useState 內部。我了解到useRef 可能是解決此問題的最佳方法,因為它可以引用可變狀態,並且在這個過程中了解了React-useStateRef,它最終解決了我的主組件內狀態永遠不會更新的另一個問題(會不斷得到「渲染太多」錯誤)。因此它本質上充當 useRef 和 useState 的合而為一。 </p> <p>但是,雖然我的狀態最終更新了,但它仍然沒有傳遞到我的 Canvas 元件。我正在嘗試根據從資料集中獲得的溫度來更新畫布的 BG 圖形。 </p> <pre class="brush:php;toolbar:false;">import { useEffect } from 'react'; import useState from 'react-usestateref'; import { getServerData } from './serviceData'; import "./App.css" import Canvas from './components/Canvas'; function App() { const [dataset, setDataset] = useState(null); const [units, setUnits] = useState('metric'); // canvas background graphic stuff var [currentBG, setCurrentBG, currentBGref] = useState(null); useEffect(() => { const fetchServerData = async () => { const data = await getServerData(city, units); setDataset(data); function updateBG() { const threshold = units === "metric" ? 20 : 60; if (data.temp <= threshold) { setCurrentBG('snow'); } else { setCurrentBG('sunny'); } } updateBG(); } fetchServerData(); console.log(currentBG) }, [city, units, currentBGref.current, currentFGref.current]) const isCelsius = currentUnit === "C"; button.innerText = isCelsius ? "°F" : "°C"; setUnits(isCelsius ? "metric" : "imperial"); }; return ( <div className="app"> { dataset && ( <Canvas width={640} height={480} currentBG={currentBGref.current}></Canvas> )} </div> ); } export default App;</pre> <p>我只能傳遞初始值,而且它永遠不會更新超過該值,儘管 useEffect 內部的 console.log 顯示它肯定正在更新。那為什麼它沒有傳遞給我的組件呢? </p>
P粉609866533P粉609866533514 天前551

全部回覆(1)我來回復

  • P粉501007768

    P粉5010077682023-09-03 00:25:16

    useStateRef似乎是一種反模式。您決定新狀態是什麼,所以如果您需要另一個對它的引用,您總是可以自己建立一個。我建議盡量減少畫布上的屬性,以防止不必要的重新渲染。

    function App({ width, height }) {
      const canvas = React.useRef()
      const [color, setColor] = React.useState("white")
    
      // when color changes..
      React.useEffect(() => {
        if (canvas.current) {
          const context = canvas.current.getContext('2d');
          context.fillStyle = color
          context.fillRect(0, 0, width, height)
        }
      }, [color, width, height])
    
      return <div>
        <canvas ref={canvas} width={width} height={height} />
        <button onClick={_ => setColor("blue")} children="blue" />
        <button onClick={_ => setColor("green")} children="green" />
        <button onClick={_ => setColor("red")} children="red" />
      </div>
    }
    
    ReactDOM.createRoot(document.querySelector("#app")).render(<App width={200} height={150} />)
    canvas { display: block; border: 1px solid black; }
    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <div id="app"></div>

    或也許沒有理由將顏色儲存為狀態。您可以自行建立setColor函數並將其附加為事件監聽器 -

    function App({ width, height }) {
      const canvas = React.useRef()
      const setColor = color => event => {
        if (canvas.current) {
          const context = canvas.current.getContext('2d');
          context.fillStyle = color
          context.fillRect(0, 0, width, height)
        }
      }
      return <div>
        <canvas ref={canvas} width={width} height={height} />
        <button onClick={setColor("blue")} children="blue" />
        <button onClick={setColor("green")} children="green" />
        <button onClick={setColor("red")} children="red" />
      </div>
    }
    
    ReactDOM.createRoot(document.querySelector("#app")).render(<App width={200} height={150} />)
    canvas { display: block; border: 1px solid black; }
    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <div id="app"></div>

    我還建議您查看SVG。我發現SVG的API與React模式更契合,比Canvas API更好。每個的功能不同,但如果SVG適用於您的需求,那麼值得考慮。

    回覆
    0
  • 取消回覆