部署fabric.js编辑器时点击添加图形出现Cannot read properties of null错误
问题原因
你遇到的线上报错Uncaught TypeError: Cannot read properties of null (reading 'add'),核心原因是FabricCanvas组件中fabric canvas的初始化时机错误:
在FabricCanvas的useLayoutEffect里,你把initCanvas(new fabric.Canvas('c'))写在了effect的清理函数(return的函数)中,这意味着这段代码只会在组件卸载时执行,而非组件挂载时。本地开发环境可能因热重载、组件重复渲染等偶然情况触发组件卸载再挂载,导致canvas被初始化;但线上生产环境组件通常只挂载一次,canvas始终为null,点击按钮调用canvas.add自然会报错。
修复方案
1. 修正FabricCanvas的canvas初始化时机
将canvas初始化逻辑从清理函数移到effect主函数中,同时添加组件卸载时的资源清理逻辑:
// FabricCanvas.js import React, { useContext, useLayoutEffect } from 'react'; import { fabric } from 'fabric'; import ContextCanvas from '../../context/ContextCanvas'; const FabricCanvas = () => { const [, initCanvas] = useContext(ContextCanvas); useLayoutEffect(() => { // 组件挂载时初始化fabric canvas const fabricCanvas = new fabric.Canvas('c'); initCanvas(fabricCanvas); // 组件卸载时销毁canvas,避免内存泄漏 return () => { fabricCanvas.dispose(); initCanvas(null); }; }, [initCanvas]); // 添加依赖,避免闭包问题 return ( <> <canvas id="c" width={window.innerWidth} height={window.innerHeight} /> </> ) } export default FabricCanvas;
2. 给ContextCanvas添加空值防护
在Context的initCanvas方法中添加空值判断,避免传入null时调用renderAll:
// ContextCanvas.js import { createContext, useState } from 'react'; import { fabric } from 'fabric'; const ContextCanvas = createContext(); export function CanvasProvider({ children }) { const [canvas, setCanvas] = useState(null); const initCanvas = c => { setCanvas(c); // 仅当canvas实例存在时调用renderAll if (c) c.renderAll(); }; return ( <ContextCanvas.Provider value={[canvas, initCanvas]}> {children} </ContextCanvas.Provider> ); } export default ContextCanvas;
3. 在添加元素的方法中增加空值检查
在FabricTextBox的addTextBox方法里先判断canvas是否存在,防止未初始化时触发错误:
// FabricTextBox.js import { fabric } from 'fabric'; import { useContext } from 'react'; import ContextCanvas from '../../../context/ContextCanvas'; import { Button } from '@chakra-ui/react'; const FabricTextBox = () => { const [canvas] = useContext(ContextCanvas); function addTextBox() { // 先检查canvas是否初始化完成 if (!canvas) return; const textbox = new fabric.Textbox('Click on the Rectangle to move it.', { fontSize: 20, left: 50, top: 100, width: 200, fill: 'black', color: 'white', cornerColor: 'blue', }); canvas.add(textbox); canvas.requestRenderAll(); } return ( <> <Button type="button" colorScheme="blue" onClick={addTextBox} variant={'ghost'} _hover={{}} _focus={{}} _active={{}} textColor={'white'} fontWeight={'light'} > Text Field </Button> </> ); }; export default FabricTextBox;
额外注意
确保你的应用根组件已经用CanvasProvider包裹,所有需要访问canvas的组件(比如FabricCanvas、FabricTextBox)都在Provider的作用域内。
内容的提问来源于stack exchange,提问作者Sohaib Ashraf
相关产品推荐
相关产品推荐

