背景
为了对antd项目进行升级,并且希望能够提供一种灵活的主题切换方式。有多套主题可以切换,每套主题下面的每个组件主题可以单独选配切换。
为了实现这样复杂的切换,我们选择了styled-components作为工具。进行了css可变属性的提取,全局样式的创建及传值。在组件内动态的传值以决定每一个组件的样式。
起初的做法是每个组件抽出一个css模板,然后将其汇总之后创建全局样式:
export const GlobalStyle = createGlobalStyle`
${spinStyle}
${Button}
${Menu}
${PaginationStyle}
${Slider}
${inputStyle}
${RadioStyle}
${selectStyle}
${AlertStyle}
${ComRadioStyle}
${tableStyle}
${switchStyle}
// ...
`;
const RootStyle = ({ theme }) => {
return ;
};
export default RootStyle;
这样,当外部传入的数据发生变化,样式会被重新编译,然后被插入style标签中。
这种模式下,样式不多的情况下没有问题,但是问题就在于我们的不止做了antd的样式,还有另外一套UI的样式。
他存在于另外一个工程,同样导出一个全局样式。两个UI合起来的组件数共有60+。这样只要主题数据发生变化,样式就会被重新编译,然后styled-components会去查找替换更改的部分,非常消耗性能。
那么如何去优化呢?
姿势一:
既然现在每次数据发生了变化所有样式都会重新编译,那么能不能只让数据变化的组件去更新样式二其它样式不变呢?当然可行的。
首先要做的就是数据的处理:我们不能每次返回一个全新的数据了,我们要先找出变动的组件,然后将其数据(也就是一个js对象)替换为新数据,其它组件数据(对象引用)保持不变。
每个组件的都导出一个全局样式。
提供一个辅助组件,用于决定当前组件是否需要更新。
所以我们的处理是这样的:
// Radio.style.js
export default createGlobalStyle`
// 样式实现在这里
`;
// index.js
const cssList = [
{ name: "input", css: inputStyle },
{ name: "alert", css: alertStyle },
{ name: "anchor", css: AnchorStyle },
{ name: "radio", css: radioStyle },
{ name: "list", css: listStyle },
// ...
];
// 遍历数据,生成一个组件列表
function getThemedComponents(list) {
return list.map(({ name, css: C }) => ({ theme }) => {
const [data, setData] = useState(theme);
useEffect(() => {
setData(prev => prev[n] !== theme[n] ? theme : prev); // 利用了stateHook 引用不变时跳过更新的特性
}, [theme]);
return (
);
});
}
// 根据cssList得到的组件列表
const components = getThemedComponents(cssList);
const RootThemeStyle = ({ theme }) => {
return (
<>
{components.map((Comp, index) => (
// eslint-disable-next-line react/no-array-index-key
))}
>
);
};
export default RootThemeStyle;
两个工程都采用这套模式之后,性能确实得到的改善,由原来的的20秒卡顿变成了12秒卡顿。但是仍然是无法接受。
如图,主要有两个耗能点,每个5秒,合起来10秒。
那么还可以怎么优化呢?
姿势二:
目前,所有的样式文件都被styled-components注入到了一个style标签内,这个标签里的内容实在太多了。每次某一段css发生了变化,都要在里面进行查找替换,极其消耗性能。
我们如果对styled-components样式注入的容器进行拆分,性能是否能够得到提升?
进行一下尝试吧。
测试记录:
激动人心。性能又提升了一半还要多。
但是!!! 时间还是有点多。
既然拆分可以提升性能。那么拆的更细呢?那么最细是什么程度?当然是每个组件创建一个style标签。所以我们直接更改辅助组件。
function getThemedComponents(list) {
return list.map(({ name, css: C }) => ({ theme, index }) => {
const [data, setData] = useState(theme);
useEffect(() => {
setData(prev => prev[n] !== theme[n]) ? theme : prev);
}, [theme]);
return (
);
});
}
我们再测试一下性能。
可以看到,耗时居然变成了0.3秒。几乎点击了之后,主题立刻就切换过来了。
我们的dom结构变成了这样的
至此,这个性能问题得到了解决。