Next.js中解构的组件className prop偶发不生效,这是bug吗?
你遇到的偶发样式失效问题和className本身的处理逻辑无关,是你的组件实现逻辑里的状态更新时序和SSR hydration不一致导致的,具体原因如下:
- 你把
classes和internalProps存在了组件state里,state更新是异步的:首次渲染时用的是state的初始值(classes是空字符串,internalProps是初始的rest),渲染完成后才会执行useEffect里的校验逻辑更新state,触发二次渲染。如果之前你没有把className从透传属性里排除,首次渲染时button上的className是props透传的原始值,二次渲染时才会替换成你合并后的classes,两次渲染结果不一致。 - 结合Next.js的SSR/SSG特性,服务端渲染输出的内容和客户端首次hydration的内容不一致时,React不会主动修正这部分属性,就会出现样式偶尔不生效的情况。
另外你的代码还有几处需要修正的问题:
- 没有从props中解构
block、size参数,直接访问会得到undefined,对应的样式类永远不会生成 - useEffect的依赖是整个props对象,组件每次渲染props都是新的引用,会导致useEffect无限触发,存在性能问题
classes和internalProps都属于完全由props派生的计算值,不需要用state存储,也不需要useEffect,直接在渲染时计算即可,能从根源避免时序问题。
正确的实现参考:
import classNames from "classnames"; const Button = (props) => { // 提取所有需要用到的自定义参数,剩下的属性透传给原生button const { className, children, block, size, ...rest } = props; // 直接计算合并后的类名,不需要state存储 const classes = classNames( "btn", className, { "btn-block": block }, { "btn-sm": size === "sm" }, { "btn-lg": size === "lg" }, ); // 如有运行时props校验需求,直接在当前位置添加校验逻辑即可 return ( <button className={classes} {...rest}> {children} </button> ); };
该实现没有多余的state和副作用,每次渲染的结果都是确定的,不会出现hydration不一致的问题,自然就不会有偶发样式失效的情况。
内容的提问来源于stack exchange,提问作者Anthony Ma
相关产品推荐
相关产品推荐

