最近考虑构建一个自己的组件库,需要考虑 CSS 样式方案
当我们构建组件库时,考虑问题的角度和普通项目可能会不太一样,不但需要考虑开发体验,同时也要照顾到使用者的感受。
CSS 的方案分为以下三种类型
组件的 CSS 和 JS 在代码层面分离,JS 里不引入样式文件,在组件库打包时分别生成独立的逻辑和样式文件。对于组件库的使用者来说,添加一个组件,需要分别引入组件代码和 CSS 文件。 假设做一个 Foo 的组件 index.tsx
React TSXimport React, { type FC } from "react"; const Foo: FC<{ title: string }> = (props) => ( <h4 className="foo">{props.title}</h4> ); export default Foo;
index.less
CSS.foo { color: red; }
如果要使用这个组件的话 要同时引入 index.tsx 和 index.less
使用案例的话 element-plus
JavaScript// main.ts import { createApp } from "vue"; import ElementPlus from "element-plus"; import "element-plus/dist/index.css";
JavaScriptimport React, { useState } from "react"; import { render } from "react-dom"; import { ConfigProvider, DatePicker, message } from "antd"; // 由于 antd 组件的默认文案是英文,所以需要修改为中文 import zhCN from "antd/es/locale/zh_CN"; import moment from "moment"; import "moment/locale/zh-cn"; import "antd/dist/antd.css"; import "./index.css";
此外 还可以借助打包工具进行按需样式自动引入,如
JavaScriptmodule.exports = { plugins: [ ["import", { libraryName: "antd", style: true }], // `style: true` 会加载 less 文件 ], };
将组件的 JS 和 CSS 打包在一起,最终只输出 JS 文件。使用时只需要引入组件就可以直接使用。
React TSXimport React, { type FC } from 'react'; + import './style.less' const Foo: FC<{ title: string }> = (props) => <h4 className='foo'>{props.title}</h4>; export default Foo;
生成的产物是
JavaScriptimport React from "react"; import "./style.less"; var Foo = function Foo(props) { return /*#__PURE__*/ React.createElement( "h4", { className: "foo", }, props.title ); }; export default Foo;
CSS.foo { color: red; }
上面这种写法对我们使用组件的项目的打包工具是有要求的,通过打包工具将 CSS 打进 JS 里。例如使用 webpack 配合 style-loader 或 rollup 配合 rollup-plugin-styles。
举 rollup-plugin-styles 的例子
rollup.config.js
JavaScriptimport styles from "rollup-plugin-styles"; export default { input: "src/main.js", output: { file: "dist/index.js", format: "cjs", }, plugins: [ styles({ mode: "inject", modules: true, }), ], };
main.js
JavaScriptimport "./index.css"; import style from "./index.module.css"; console.log(style);
index.css
CSSbody { height: 100vh; background-color: pink; } @import url("./style.css");
index.module.css
CSS.foo { color: blue; } @import url(./style.css);
style.css
CSS.bar { color: red; }
在这个方案下,不存在 CSS 文件,一切都是 JS,对打包工具是没要求了,但是有运行时的性能消耗,而且作为组件库不好样式覆盖 「但也有解,见下一节」
React TSXimport styled from "styled-components"; const Title = styled.h1` font-size: 1.5em; text-align: center; color: palevioletred; .foo { color: red; &:hover { background: blue; } } `; function Demo1() { return ( <div> Demo1 <Title> I am Happy <div className="foo">foo</div> </Title> </div> ); }
CSS in JS 最大的优势便是灵活,注意到 antd 5 和 mui 都选用了 CSS in JS 的方案
https://mui.com/material-ui/customization/how-to-customize/
antd5 做的很棒,在样式方面借助 :where() 结合 cssinjs 的 hash 选择器,我们可以让组件的样式始终处于在 hash 的范围之下,这可以保证组件样式不会对全局样式造成任何污染,又方便覆盖 https://ant-design.github.io/antd-style/guide/components-usage
最终选择用 antd-style 来做基于 antd 的二次开发,很舒服
下面是写的一个小 demo
Foo/index.tsx
React TSXimport React from "react"; import { createStyles } from "../utils"; const compCls = `foo`; const useStyles = createStyles(({ token, css, prefixCls, cx }) => { const prefix = [prefixCls, compCls].join("-"); return { container: cx( prefix, css` &.${prefix} { background-color: ${token.colorPrimaryBg}; } ` ), card: cx( `${prefix}-card`, css` &.${prefix}-card { color: ${token.colorPrimary}; } ` ), }; }); const Foo = (props: { title: string }) => { const { styles } = useStyles(); return ( <div className={styles.container}> <div className={styles.card}>{props.title}</div> </div> ); }; export default Foo;
utils.ts
React TSXimport { createInstance } from "antd-style"; export const { createStyles, ThemeProvider } = createInstance({ hashPriority: "low", prefixCls: "sedationh", });
https://juejin.cn/post/7097100515535765534 https://blog.pig1024.me/posts/62fa1f4e8631e51c9414da21