代码之家  ›  专栏  ›  技术社区  ›  Jay White

useRef。current存在,但根据作用域的不同为空

  •  1
  • Jay White  · 技术社区  · 3 年前

    我试图用React和布料在画布上画一幅图像。js。
    Demo(CodeSandBox)

    在上面的演示中,当按下“Draw image”(绘制图像)按钮时,人们会期望在画布上立即绘制图像,但不会在画布上绘制图像。

    ref current null

    没有画出这幅图像的原因似乎是 useRef 执行函数在画布上绘制图像时为null。(如果第50行的isInput条件被删除, containerRef.current 不再为空,可以在画布上绘制图像。)

    你能帮我在画布上画一幅画吗?


    代码如下。

    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;
    

    我明白 containerRef。现在的 如果没有DOM,则为null。

    然而

    1. 去哪个部门 containerRef 是由 setIsInput(true)
    2. containerRef。现在的 从里面得到的 useEffect 不是空的。

    从以上几点来看,我们可以预期 containerRef。现在的 即使在drawImg函数中也不为null。

    1 回复  |  直到 3 年前
        1
  •  1
  •   Nicholas Tower    3 年前

    使命感 setIsInput(true) 只是请求重新启动组件。这种情况很快就会发生,但一般来说 立即同步发生。所以你的代码在 设置输入(真) 无法假定ref已更新。

    通常情况下,你可以分两步来完成。首先,重新渲染组件,然后将图像绘制到画布上。这可以通过将绘图代码放入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 可以强制同步呈现状态更新。

    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();
      });
    };
    

    一定要查看下表中的注释 documentation for flush sync ,因为如果你选择使用它会有后果。