React+Emotion中:nth-child触发SSR报错疑问:客户端为何出现该提示?
问题场景
我在React项目中用Emotion给特定div写样式,代码是这样的:
export const StyledItem = styled('div')(() => ({ '&:nth-child(1) > div:nth-child(1)': { paddingBottom: '1px' } }))结果Chrome控制台弹出报错:
The pseudo class ":nth-child" is potentially unsafe when doing server-side rendering. Try changing it to ":nth-of-type"。换成:nth-of-type之后报错就没了。我的项目依赖是:"dependencies": { "@emotion/core": "^10.0.28", "@emotion/styled": "^10.0.27", "react": "^16.13.1", "@storybook/react": "^5.3.13", /* 其他依赖 */ }明明我是在客户端运行项目,为啥会出现SSR相关的报错提示?
原因拆解
这个问题其实和你当前是否在客户端运行无关,主要是两个因素共同导致的:
Emotion的全局安全校验逻辑
Emotion的样式解析器对所有选择器都会做统一的安全检查,不管你的项目是纯客户端还是SSR。:nth-child的匹配规则依赖父元素下所有子元素的位置排序——如果在SSR场景下,服务端和客户端渲染的DOM结构有一丁点差异(比如某个子元素的渲染条件不同),就会导致样式匹配不一致,触发hydration失败。所以Emotion直接把这类选择器标记为“潜在不安全”,这个校验是全局生效的,不会区分运行环境。Storybook的环境影响
你项目里用到的@storybook/react@5.3.13,这个版本的Storybook内部其实包含了SSR相关的逻辑(哪怕你是在浏览器里查看Storybook)。Emotion会检测到环境中存在SSR相关的标识,从而触发这个安全提示,哪怕你实际并没有做项目的SSR渲染。
可选的解决方式
除了你已经试过的替换为:nth-of-type,还有两个方向可以选择:
- 用
unsafeSelector绕过校验(不推荐)
如果你百分百确定自己的场景不会涉及SSR,也不会出现DOM结构不一致的问题,可以用Emotion提供的unsafeSelector跳过检查:
import { unsafeSelector } from '@emotion/styled' export const StyledItem = styled('div')(() => ({ [unsafeSelector('&:nth-child(1) > div:nth-child(1)')]: { paddingBottom: '1px' } }))
但要注意,这么做相当于关闭了Emotion的安全防护,后续如果项目引入SSR,很容易出现hydration错误,所以谨慎使用。
- 升级Emotion到新版本
你现在用的是Emotion 10的旧版本,在Emotion 11及以后的版本中,这个提示逻辑做了优化,会更精准地判断当前环境是否真的是SSR,减少不必要的报错。如果项目允许的话,升级到最新稳定版是更稳妥的长期方案。
内容的提问来源于stack exchange,提问作者norbitrial

