簡體   English   中英

useRef.current 存在,但是是 null 取決於 scope

[英]useRef.current exists, but is null depending on scope

我正在嘗試使用 React 和 Fabric.js 在 canvas 上繪制圖像。
演示(代碼沙盒)

在上面的演示中,當按下“繪制圖像”按鈕時,人們會期望立即在 canvas 上繪制圖像,但在 canvas 上沒有繪制任何圖像。

參考當前空

畫不出來的原因好像是執行function在canvas上畫圖時,useRef的current是useRef (如果去掉第50行的isInput條件, containerRef.current就不再是null了,圖片可在canvas上繪制。)

你能幫我在canvas上畫個圖嗎?


代碼如下。

const App = () => {
  const [img, setImg] = useState(defaultImg);
  const changeImg = ({ target: { value } }: { target: { value: string } }) =>
    setImg(value);

  const [isInput, setIsInput] = useState(false);
  const containerRef = useRef<HTMLDivElement>(null);
  const canvasRef = useRef<HTMLCanvasElement>(null);

  const checkCurrent = (caller: string) => {
    console.log(`[${caller}]Container: ${containerRef.current}`);
  };
  useEffect(() => {
    if (!isInput) return;

    checkCurrent("useEffect");
  }, [isInput]);

  const drawImg = (e: { preventDefault: () => void }) => {
    e.preventDefault();
    if (!img) return;
    setIsInput(true);

    checkCurrent("drawImg");

    canvas = new fabric.Canvas("canvas");
    fabric.Image.fromURL(img, (imgObj: fabric.Image) => {
      canvas.add(imgObj).renderAll();
    });
  };

  return (
    <div className="App">
      {!isInput && (
        <form onSubmit={drawImg}>
          Demo image url
          <input type="text" onChange={changeImg} defaultValue={img} />
          <button type="submit">Draw image</button>
        </form>
      )}

      {isInput && (
        <>
          <button onClick={() => setIsInput(false)}>Reset</button>
          <div ref={containerRef} className="canvas_container">
            <canvas ref={canvasRef} id="canvas" />
          </div>
        </>
      )}
    </div>
  );
};

export default App;

我知道如果沒有 DOM, containerRef.current是 null。

然而,

  1. 設置containerRef的 div 由setIsInput(true)繪制
  2. useEffect里面得到的containerRef.current不是null。

從以上幾點可以看出,即使在drawImg function內部, containerRef.current也不是null。

調用setIsInput(true)只是請求重新渲染組件。 這將很快發生,但通常不會立即同步發生。 因此,您在setIsInput(true)之后的行中的代碼不能假定 ref 已經更新。

所以通常,您會分兩步執行此操作。 首先,重新渲染組件,然后將圖像繪制到 canvas。這可以通過將繪圖代碼放入 useEffect 來完成,因為正如您所注意到的,這發生在填充 ref 之后(因為它發生在渲染完成之后)刷新到 dom):

const drawImg = (e: { preventDefault: () => void }) => {
  e.preventDefault();
  if (!img) return;
  setIsInput(true);
}

useEffect(() => {
  if (!isInput) return;

  const canvas = new fabric.Canvas("canvas");
  fabric.Image.fromURL(img, (imgObj: fabric.Image) => {
    canvas.add(imgObj).renderAll();
  });
}, [isInput]);

由於您使用的是 React 18,所以還有一個額外的選項需要提及,但我建議您不要使用它,除非您確實需要它。 React 18 添加了一個名為flushSync的新 function,它可以強制同步呈現 state 更新。

import { flushSync } from 'react-dom'; // Note: 'react-dom', not 'react'

// ...

const drawImg = (e: { preventDefault: () => void }) => {
  e.preventDefault();
  if (!img) return;

  flushSync(() => {
    setIsInput(true);
  });

  canvas = new fabric.Canvas("canvas");
  fabric.Image.fromURL(img, (imgObj: fabric.Image) => {
    canvas.add(imgObj).renderAll();
  });
};

請務必查看文檔中有關 flush sync的注釋,因為如果您選擇使用它會產生后果。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM