React useEffect中ref设置异常导致canvas上下文修改属性失效
这是预期行为,既不是React的bug,也不是代码逻辑错误,问题根因是canvas元素的原生API特性和React渲染流程结合导致的。
根因分析
- 首先明确canvas的原生规则:只要你修改
<canvas>元素的width或heightHTML属性,无论值有没有变化,浏览器都会完全重置整个2D绘图上下文,所有之前修改的样式属性(比如lineCap、strokeStyle、fillStyle等)都会回到默认值,画布内容也会被全部清空。 - 你的代码渲染时序刚好触发了这个特性:
- 父组件首次渲染时,
canvasWidth和canvasHeight的初始值是0,子组件拿到的初始prop就是0,此时canvas的宽高属性被设置为0 - 子组件空依赖的
useEffect触发,你拿到上下文修改了lineCap = 'round',并存到contextRef,这时候属性是正常生效的 - 父组件的
useLayoutEffect在挂载后执行,拿到容器真实尺寸,调用setState更新宽高,触发父子组件重渲染 - 子组件拿到新的宽高prop,更新到canvas的
width和height属性,触发了canvas原生的上下文重置,你存在contextRef里的还是同一个上下文对象,所以lineCap变回了默认值butt
- 父组件首次渲染时,
为啥把宽高加到依赖里就解决了?
当你把canvasWidth和canvasHeight加到useEffect的依赖数组后,每次宽高更新,useEffect都会重新执行:
刚好在canvas宽高更新、上下文被重置之后,你重新给上下文设置lineCap = 'round',自然就生效了。
优化方案
如果不想每次宽高变化都重复执行所有逻辑,也可以加判断,等宽高首次拿到有效值之后再初始化上下文,避免无效的首次初始化:
useEffect(() => { const canvas = canvasRef.current; // 等宽高都有有效值再初始化 if (!canvas || props.canvasWidth <= 0 || props.canvasHeight <= 0) return; const context = canvas.getContext('2d'); if (!context) return; context.lineCap = 'round'; contextRef.current = context; }, [props.canvasWidth, props.canvasHeight]);
内容的提问来源于stack exchange,提问作者Sandrow
相关产品推荐
相关产品推荐

