【山竹记账前端-react】4.CSS in JS 三、四种方案
大纲链接 §
[toc]
0. 引入样式的准备工作 ⇧
使用SCSS预编译 ⇧
- 使用
Vite运行开发服务器,默认支持CSS Modules - 如果使用了预编译器,例如
SCSS,需要额外安装pnpm add -D sass-embedded
- 安装完成重启服务即可
引入重置样式
@/assets/styles/reset.scss
|
|
@/main.scss
|
|
@/main.tsx
|
|
1. 方案一:CSS Modules ⇧
创建
@/pages/Welcome1.tsx的模块样式@/pages/Welcome1.module.scss
- 文件名称前缀规定一致
Xxx.module.scss 文件路径在组件同级目录下即可
1 2 3 4 5.wrapper { color: red; &:hover {color: lightseagreen;} }
修改
@/pages/Welcome1.tsx
|
|
- 标签上的样式属性名称为
className={s.wrapper} - 节点
<div className={s.wrapper}> Welcome1 </div>- 会被编译为
<div class="_wrapper_1d30h_1">Welcome1</div> - 自动添加哈希后缀来隔离样式
- 即不同组件及时样式名称重名也不会互相影响
- 会被编译为
CSS Modules只对于className属性起效- 使用
id选择器无效
- 使用
CSS Modules 使用多个类样式拼接 ⇧
相较于
Vue SFC中写法,TSX中多个类不支持使用数组或对象直接拼接,需要引入拼接的方法
- 历史上合并类样式的解决方案:
- 使用
classnames传统CSS Modules框架,初步基础摆脱繁琐的字符串三元表达式拼接 import cs from 'classnames'<div className={c([s.wrapper, s.xxx, 'abc'])}></div>- 使用
:globle(.abc)处理纯字符串样式,编译的样式上则不加随机哈希 - 使用
clsx现代CSS Modules首选,现代轻量化拼接工具 - 用法和
classnames相同 - 使用
cva (Class Variance Authority)- 类型安全,适用于Tailwind类样式合并拼接 - 使用
@styled-cva/react- 适用于TailwindStyled类样式合并拼接
- 使用
- 本项目使用
shadcn/cn,综合了twMerge和clsx的功能
相当于
|
|
- 安装
pnpm i cn 在
@/views/WelcomeView.tsx尝试使用1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21import { Outlet } from 'react-router-dom' import { cn } from 'cn' import s from '@/modules/welcome/WelcomeView.module.scss' import logo from '@/assets/icons/mangosteen.svg' export const WelcomeView: React.FC = () => ( <div class={s.wrapper}> <header class={s.title}> <img src={logo} alt="logo" /> <h1>山竹记账</h1> </header> <main class={cn([ 'bg-welcomeCardBg mb-62px ml-16px mr-16px rounded-lg', 'flex flex-col flex-grow items-center justify-around', ])}> <Outlet /> </main> <section class={s['to-last-page']}>跳过</section> </div> )
参考
2. 方案二:Style Components ⇧
使用
Style Components
- 安装
pnpm add styled-components@6.5.3 - 引入
import styled from 'styled-components' 改写
@/pages/Welcome2.tsx1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26import { NavLink } from 'react-router-dom' import styled from 'styled-components' const BorderedDiv = styled.div` color: darkblue; border: 1px solid darkred; &:hover { background-color: yellow; } ` const BlueBox = styled(BorderedDiv)` border-color:blue; ` export const Welcome2: React.FC = () => { return ( <BorderedDiv> welcome 2 <NavLink to="/welcome/3">下一页</NavLink> <BlueBox>hi</BlueBox> </BorderedDiv> ) }
节点编译结果
- 全随机样式类名
<div class="sc-eCIkAO hsqKMF">welcome 2<a class="" href="/welcome/3">下一页</a></div>- 查看编译结果样式
.hsqKMF {color: darkblue; border: 1px solid darkred;}
适用场景
- 不适合于写固定类名,不用自己命名类名,不适合基于类名的单元测试查找
- 需要在组件中每次额外引入依赖
- 适合于不新建单独的
CSS文件,在当前组件中写样式组件 - 复用样式组件
const BlueBox = styled(BorderedDiv)...
代码提示
VSCode可以安装官方插件styled-components.vscode-styled-componentsnyxb.vscode-styled-colorsjonkwheeler.styled-components-snippets
参考
- styled-components CSS for the
Age - npm styled-components
- @styled-cva/react
- 【项目-React-3】styled-components 的威力与完成 Money.tsx 组件样式结构模块化拆分
- 如何使用单元测试来 测试 styled-components 选取的类名
3. 方案三:Uno CSS ⇧
使用步骤 ⇧
安装
pnpm add -D unocss
或者一起安装配套的预设
1pnpm install -D unocss @unocss/vite @unocss/preset-attributify @unocss/preset-wind4
配置
vite.config.ts
|
|
- 需要注意的是,之后使用了
presetAttributify,此时插件的书写顺序:plugins: [UnoCSS(), react(),],
初始添加配置
uno.config.ts
|
|
presetAttributify预设允许书写类似<div b-1 b-red>Uno CSS</div>
引入到
main.tsx
|
|
import 'virtual:uno.css'初始为空,使用一个类就会实时创建一个类的样式
尝试改造组件 ⇧
src/pages/Welcome3.tsx
|
|
flex属性并不被tsx类型识别需要额外添加
src/shims.d.ts1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18import { HTMLAttributes } from 'react' import type { AttributifyAttributes } from '@unocss/preset-attributify' declare module 'react' { interface HTMLAttributes<T> extends AriaAttributes, AttributifyAttributes, DOMAttributes<T> { // 允许传入 UnoCSS 属性 bold?: boolean text?: string bordered?: boolean bg?: string flex?: string | boolean block?: boolean relative?: boolean absolute?: boolean // 或者直接使用通配符或特殊定义 [key: string]: unknown } }之后如果出现未识别的属性可补充
可以兼容
CSS Module方案 不冲突
|
|
举例伪元素的写法
|
|
添加额外的 uno.config.ts 配置
|
|
写法快速参考网站 ⇧
- 写出原本的
css代码 - 丢到ai搜索,给出
unocss写法 - 查询对应写法
可以做到 “不写CSS” ⇧
- 原子类库
<div b-1 b-red text-xxl flex grid bg-red></div> - 原子类库
<div class="b-1 b-red text-xxl flex grid bg-red"></div> Tailwind CSS原子类过去曾生成每一个原子类,造成超级臃肿的CSS文件Windi CSS首次引入动态生成JIT扫描文件动态生成原子类样式Tailwind CSS后来也有了自己的Just In Time动态生成功能Windi CSS后来停止维护Uno CSS继承遗志,灵活定制化
参考
推荐 VSCode 插件:unocss ⇧
- 安装链接:https://marketplace.visualstudio.com/items?itemName=antfu.unocss
- 这个插件可以在后面的课程帮助你理解 unocss 的每个属性
- 这个插件会在你鼠标悬浮到 unocss 属性上时,提示其对应的 CSS 代码是什么,如下图:
4. 方案四:JSX Scoped ⇧
jsx-scoped —— 面向 JSX/TSX 的 Vue-like scoped 样式工具链
- 把组件文件属性 绝对路径哈希成
data-v-{hash} JSX元素自动带上该属性,编译后选择器追加[data-v-{hash}],规则只命中本组件DOM- 1.使用 单独文件
*.scoped.*类似*.mudule.* - 2.在
JSX使用内联标签<style scoped>
- 1.使用 单独文件
使用步骤 ⇧
安装
|
|
在
vite.config.ts中配置
|
|
react中需要配置jsxScoped({ scopedIdAttributeName: 'scopedid' }),,否则会报以下错误:1 2 3Warning: React does not recognize the `scopedId` prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase `scopedid` instead. If you accidentally passed it from a parent component, remove it from the DOM element. Error Component Stack
配置 TS 类型声明
tsconfig.app.json
|
|
添加
@/pages/Welcome4.scoped.scss
|
|
引入到
@/pages/Welcome4.tsx
|
|
- 内联的标签中没有美化的格式化提示,目前还没有相关插件
编译结果 ⇧
类似
Vue style scoped
|
|
|
|
参考
6. 总结 ⇧
| 特征维度 | CSS Modules | Styled Components | UnoCSS | JSX Scoped |
|---|---|---|---|---|
| 技术本质 | 编译时提取(原生 CSS 增强) | 运行时 CSS-in-JS | 编译时静态生成(原子化) | 编译时转换(Vue 风格局部作用域) |
| 隔离机制 | 哈希混淆类名 (.title__[hash]) |
动态生成随机类名 (.sc-xyz) |
原子化类名,无传统意义“冲突” | 属性选择器 (.title[data-v-xyz]) |
| 代码组织 | JS 与 CSS 分离 (.module.css) |
样式与组件一体化 (All in JS) | 标签内联属性 / 样式简写 | 灵活:既可单文件内联 <style scoped>,也可单独创建 *.scoped.css 分离文件 |
| 类名引用方式 | 需具名导入 变量映射className={styles.title} |
无需类名 包装成组件<Title /> |
无需类名 直接写原子类className="p-4 bg-white" |
无需具名导入 直接写原生字符串 className="title" |
| 动态样式 | 依赖 JS 动态拼接类名 (clsx) |
完美支持 Props 传参、运行时计算 | 依赖预设的动态类名/安全列表 | 需配合行内 style 或属性切换 |
| 运行时开销 | 无 (零开销) | 有 (需要解析、注入、Hash 计算) | 无 (零开销) | 无 (零开销) |
| 打包体积 | 提取为普通 .css 文件 |
包含较大运行库,且样式混在 JS 中 | 极小(只打包用到的原子类 CSS) | 提取为普通 .css 文件 |
参考
参考文章
- 初始代码:https://github.com/FrankFang/t4zuC0vJ
- 最终代码:https://github.com/FrankFang/88wqje5K
- 提交历史:https://github.com/FrankFang/88wqje5K/commits/master
为何最终代码中的 shims.d.ts 与视频中不一样?
- 这是因为视频中的 shims.d.ts 在后面的课程中造成 VSCode 提示失灵并且非常卡顿,所以修改了 shims.d.ts。
相关文章
- 请对比 React Tsx 中 CSS in JS 的四种方案
- JSX Scoped CSS
- CSS Modules
- UnoCss
- UnoCss Vite Plugin
- Vue3 TSX UnoCss
- 在 Vue 3 + TS + Vite + TSX 项目中配置 UnoCSS
- UnoCSS, Vite + Vue3 easy setup
- stackblitz unocss-unocss
- primevue UnoCSS
- vite-vue3-tsx-pinia-unocss
- unocss-ui
- How to setup VueJS 3 with Vite, TypeScript, UnoCSS, ESLint (flat config) and Prettier
- UnoCss 过时的预设
@unocss/preset-uno - npm @unocss/preset-uno This package is deprecated and renamed to @unocss/preset-wind3
- Uno preset Uno 预设
- UnoCss Official Packages
- Wind4 preset
- tailwindcss
- windicss
cn clsx cva @styled-cva/react- npm @styled-cva/react
- Tailwind Variants A first-class variant API for Tailwind CSS
- class module vs cva vs clsx 类模块与 CVA、CLSS 之间的对比
- 用禁止广告插件会404匿名模式打开:cva & cn in Tailwind CSS: Conditional Classes in React
- shadcn/ui 里的 cva vs. cn():现代 React 组件库里真的需要这两个吗?
- How to Use the clsx Library
- github clsx
- shadcn 新开源 cn:干掉 clsx+tailwind-merge,Tailwind 类合并引擎提速 30 倍
- github cn
- unocss shadcn/cn,代替 clsx+tailwind-merge
- 作者: Joel
- 文章链接:
- 版权声明
- 非自由转载-非商用-非衍生-保持署名