背景
开门见山,我们在解该问题前先做一个 css in js 插入 style 标签的前提介绍:
方案 | 说明 |
---|---|
快速方案A | 目前使用现代 css style sheet 也就是 cssom 的 api 去操作样式是性能最好的,会把一堆 css 放到一个 <style></style> 标签里,这种方案速度很快,支持万级别的样式插入 |
慢速方案B | 而早期是一个 style 标签对应插入一个样式,这样会比较慢,但是他在开发环境会很方便于修改和调试 |
问题
问题是什么呢?
问题是在 qiankun
里,非第一次加载同一个子应用时(比如切换了子应用或者在主应用和子应用间切换),会随机性产生丢失 cssom 的样式问题,可见相关 issue :
解法
有了以上基础知识铺垫,很明显,既然是 cssom 导致的,那我们只需要将其 快速方案A
切换为 慢速方案B
,返璞归真即可。
以下介绍两个库( styled-components
和 emotion
)的具体解法,其他库均同理,请自行探索源码。
styled-components
sc 有两种解法,为了回退到 B 旧插入模式,配置外层 Context 即可:
import { StyleSheetManager } from 'styled-components'
export default function App() {
return (
<StyleSheetManager disableCSSOMInjection>
{/* ... */}
</StyleSheetManager>
)
}
此处 disableCSSOMInjection
即代表回退到旧式单 style 对应单 css 方案。
- 官方 api 文档:disableCSSOMInjection
除此之外,还可以通过环境变量解,以下是源码片段,我们来看两个逻辑:
// 默认值逻辑
const defaultOptions: SheetOptions = {
isServer: !IS_BROWSER,
// ↓ 这里是该 option 的默认值获取处
useCSSOMInjection: !DISABLE_SPEEDY,
};
// ↓ 通过环境变量判断了默认取值
export const DISABLE_SPEEDY = Boolean(
typeof SC_DISABLE_SPEEDY === 'boolean'
? SC_DISABLE_SPEEDY
: typeof process !== 'undefined' &&
typeof process.env.REACT_APP_SC_DISABLE_SPEEDY !== 'undefined' &&
process.env.REACT_APP_SC_DISABLE_SPEEDY !== ''
? process.env.REACT_APP_SC_DISABLE_SPEEDY === 'false'
? false
: process.env.REACT_APP_SC_DISABLE_SPEEDY
: typeof process !== 'undefined' &&
typeof process.env.SC_DISABLE_SPEEDY !== 'undefined' &&
process.env.SC_DISABLE_SPEEDY !== ''
? process.env.SC_DISABLE_SPEEDY === 'false'
? false
: process.env.SC_DISABLE_SPEEDY
: process.env.NODE_ENV !== 'production'
);
也就是说你可以通过如下配置 env 环境变量实现默认关闭:
// .env
SC_DISABLE_SPEEDY=false
// or (in cra, `REACT_APP` prefix env will auto inject)
REACT_APP_SC_DISABLE_SPEEDY=false
emotion
emotion 的解法也同理,配置 speedy
为 false
即可,注意你需要提前安装 @emotion/cache
包来提供外层自定义 Context,因为 emotion 会缓存 style 到 cache store 内:
pnpm add @emotion/cache
配置 Cache Context:
import createCache from '@emotion/cache'
import { CacheProvider } from '@emotion/react'
const myCache = createCache({
key: 'x',
speedy: false,
})
export default function App() {
return (
<CacheProvider value={myCache}>
{/* ... */}
</CacheProvider>
)
}
值得一提的是,emotion 这里必须要配置唯一 key
作为所有样式的 prefix ,是一个不太好的地方,也就是说至少给我们带来了 n * 1
个字节的体积增大( n
为 emotion 使用次数 )。
快速带大家看下源码:
// 片段 1:默认值逻辑
constructor(options: Options) {
this.isSpeedy =
options.speedy === undefined
? process.env.NODE_ENV === 'production'
: options.speedy
}
// --------------------------------------------
// 片段 2:插入标签部分逻辑
insert(rule: string) {
// 🌈 这里如果是 false 就会只插入一个标签
if (this.ctr % (this.isSpeedy ? 65000 : 1) === 0) {
this._insertTag(createStyleElement(this))
}
// ...省略很多行
if (this.isSpeedy) {
const sheet = sheetForTag(tag)
// ...省略很多行
} else {
// 🌈 非 cssom 式操作
tag.appendChild(document.createTextNode(rule))
}
}
如果以上源码都看不懂也无所谓,只要记住 speedy
这个词是代表快速插入即可,如果是其他 css in js 库可以朝着 speedy
关键词找。
另外需要注意的是,在开发环境下,为了便于调试和修改,不管是 sc 还是 emotion 默认都开启了 慢速模式B
(可在上面的源码片段中追溯),这也是在本地不会发现端倪构建后才出现问题的一个点。
总结
最后我们总结思考下:
-
为了避免
qiankun
问题,不改动 qiankun 代码的前提下,回退到B方案
是目前唯一解,因此选择 sc 还是 emotion 谁快谁慢应该没区别了,也就是说享受不到 cssom 还是库优化的好处。 -
emotion 使用姿势很多,sc 比较单一,但 emotion 多姿势的代价是要兼容他的 css 对应的 jsx runtime 注入,这需要额外的 babel 工作量。
-
回退到
B方案
时,sc 配置比较灵活(甚至支持环境变量隐式配置),而 emotion 配置稍复杂,还存在体积增大的副作用。 -
用 css in js 就要 babel 插件压缩,同时有了 babel 插件才能在开发时显示类名,而到了 swc 未来,nextjs 正在写 sc 的 swc 插件,有 roadmap,进度虽然缓慢,但 emotion 的 swc 插件却还没打算写,甚至都不用说进度了。
综合来看,建议使用 sc 比较面向未来,当然是在 qiankun 场景下,如果非 qiankun 场景,建议使用 emotion 。