React中Css几种实现方案
全局样式
与传统 html 标签类属性不同,react 中 class 必 须编写为 className,比如
全局 css
.box {
background-color:red;
width:300px;
height:300px;
}
js
function Hello() {
return <div className="box">hello react</div>
}
ReactDOM.render(<Hello />, document.getElementById('root'))
与传统在 html 标签定义 css 样式不同,因为这不是传统的 html 代码,而是 JSX,由于 class 作为关键字,无法作为标识符出现,比方说下面的代码将会报错。
const { class } = { class: 'foo' } // Uncaught SyntaxError: Unexpected token }
const { className } = { className: 'foo' }
const { class: className } = { class: 'foo' }
关于官方也有对此问题回答
有趣的话题,为什么 jsx 用 className 而不是 class
所以把传统的 html 代码强行搬运到 react 中,如果带有 class 与 style 属性,那么将会报错。
内联样式
内联样式也得写成对象 key-value 形式,遇到-连字符,则需要大写,如
function Hello() {
return (
<div className="box" style={{ fontSize: '32px', textAlign: 'center' }}>
hello react
</div>
)
}
CSS 的font-size
属性要写成fontSize
,这是 JavaScript 操作 CSS 属性的约定。
其实 可传入表达式,比方这里传入的就是{ fontSize: "32px",textAlign: "center" }
对象,也可以将其定义为一个变量传入。
但是写内联样式显得组丑陋影响阅读,并且样式不易于复用,同时伪元素与媒体查询无法实现,但是封装成类样式,又会影响到全局作用域,所以便有了局部样式styles.module.css
。
局部样式 CSS Modules
Css Modules 并不是 React 专用解决方法,适用于所有使用 webpack 等打包工具的开发环境。以 webpack 为例,在 css-loader 的 options 里打开modules:true
选项即可使用 Css Modules。一般配置如下
{
loader: "css-loader",
options: {
importLoaders: 1,
modules: true,
localIdentName: "[name]__[local]___[hash:base64:5]" // 为了生成类名不是纯随机
},
},
然后通过 import 引入
import styles from './styles.module.css'
function Hello() {
return <div className={styles.box}>hello react</div>
}
但如果是有多个局部样式,直接拼接是无效的(毕竟是个无效的表达式)
// 错误
<div className={style.class1 style.class2}</div>
// 正确
<div className={`${style.class1} ${style.class2}`}</div>
<div className={style.class1+ " " +style.class2}</div>
<div className={[style.class1,style.class2].join(" ")}</div>
classnames
还可以通过 npm 包 classnames 来定义类名,如
import classnames from 'classnames'
import styles from './styles.module.css'
;<div className={classnames(styles.class1, styles.class2)}></div>
最终都将编译为
<div class="class1 class2"></div>
当然 classnames 还有多种方式添加,就不列举了,主要针对复杂样式,根据条件是否添加样式。
但是 在 Css Module 中,其实能发现挺多问题的
如果类名是带有-连字符.table-size
那么就只能styles["table-size"]
来引用,并且都必须使用{style.className}
形式。
最主要的是,css 都写在 css 文件中,无法处理动态 css。
CSS in JS
由于 React 对 CSS 的封装非常弱,导致了一系列的第三方库,用来加强 CSS 操作,统称为 CSS in JS,有一种在 js 文件中写 css 代码的感觉,根据不完全统计,各种 CSS in JS 的库至少有47 种,其中比较出名的 便是styled-components。
import styled from 'styled-components'
// `` 和 () 一样可以作为js里作为函数接受参数的标志,这个做法类似于HOC,包裹一层css到h1上生成新组件Title
const Title = styled.h1`
font-size: 1.5em;
text-align: center;
color: palevioletred;
span {
font-size: 2em;
}
`
// 在充分使用css全部功能的同时,非常方便的实现动态css, 甚至可以直接调用props!
const Wrapper = styled.section`
padding: 4em;
background: ${props => props.bgColor};
`
const Button = styled.a`
/* This renders the buttons above... Edit me! */
display: inline-block;
border-radius: 3px;
padding: 0.5rem 0;
margin: 0.5rem 1rem;
width: 11rem;
background: transparent;
color: white;
border: 2px solid white;
/* The GitHub button is a primary button
* edit this to target it specifically! */
${props =>
props.primary &&
css`
background: white;
color: palevioletred;
`}
`
const App = () => (
<Wrapper bgColor="papayawhi">
<Title>
<span>Hello World</span>, this is my first styled component!
</Title>
<Button
href="https://github.com/styled-components/styled-components"
target="_blank"
rel="noopener"
primary
>
GitHub
</Button>
</Wrapper>
)
像上面的 Title,Wrapper,Button 都是组件,Title 本质就是一个 h1 标签,在通过模板字符串编写局部 css 样式。
能直接编写子元素的样式,以及& :hover
等 Sass 语法。
根据传入属性,在 css 中使用,Wrapper 传入背景颜色属性,Button 判断是否为 primary。
并且能方便的给暴露className
props 的三方 UI 库上样式:
const StyledButton = styled(Button)` ... `
styled-jsx
vercel/styled-jsx: Full CSS support for JSX without compromises (github.com)
styled-jsx 概括第一印象就是 React css 的 vue 解决。yarn add styled-jsx
安装后,不用import
,而是一个 babel 插件,.babelrc
配置:
{
"plugins": [
"styled-jsx/babel"
]
}
使用
render () {
return <div className='table'>
<div className='row'>
<div className='cell'>A0</div>
<div className='cell'>B0</div>
</div>
<style jsx>{`
.table {
margin: 10px;
}
.row {
border: 1px solid black;
}
.cell {
color: red;
}
`}</style>
</div>;
}
只会作用到同级标签作用域,可以说是一种另类的内联样式了,如果不喜欢将样式写在 render 里,styled-jsx 提供了一个 css
的工具函数:
import css from 'styled-jsx/css'
export default () => (
<div>
<button>styled-jsx</button>
<style jsx>{button}</style>
</div>
)
const button = css`
button {
color: hotpink;
}
`
补充:现在我更推荐使用 Emotion。
原子类
简单说,就是将常用的 css 样式都封装完,只需要在 class 中引入即可
这里选用当红框架 Tailwind CSS 作为演示。
比方说 flex 布局的话,就需要写 dispaly: flex;
但是封装成类,如
.flex {
dispaly: flex;
}
引用的时候直接在 class 中添加 flex 即可
<h1 class="flex">tailwindcss</h1>
贴一张官方演示图,把大部分常用的样式都封装成 class
官方在线例子(下图) Tailwind Play (tailwindcss.com)
有以下几种优点:
- 源代码无非就是 css 的基本样式,如 class
w-auto
对应 csswidth: auto;
等等 - 如果不是特别复杂的样式,甚至可以不用写一条 css 代码,开发效率杠杠的。
- 体积很小,更好的样式复用,并且打包后会根据所用的 class 进行打包,而非全部无用样式打包。
- 与 bootstrap 设计不同,完全可以定制化不同类型的组件,而不是像
class="btn btn-danger"
这样。
体验下来基本上就是在写内联样式 inline css 但是同时又不显得杂乱。
组件化中使用
在组件化开发中,完全可以自己实现一个 Button 按钮(上间距 pt-4
,底部间距 pb-10
,文字为 text-sky-500
天蓝色),
const Button = ({ children, color }) => (
<a className=`pt-4 pb-10 text-sky-500 ${color}`>{children}</a>
)
不过要说缺点的话:
- 可能之前标题只需要定义.title 类来完成全部样式,而 tailwind 需要好几个 css 原子类来实现
- 初学者可能不适应,需要反复的查阅文档。(不过用多了,自然就会习惯了)
然后还有一个 WindCSS,可以看作是按需供应的 Tailwind 替代方案。不过暂时不支持 React。
此外还有一篇文章非常推荐 重新构想原子化 CSS (antfu.me),不多说,再刷一遍。
最佳实现?
介绍完几种 React 中 Css 的实现(当然还有很多库没介绍,主要挑几种主流的),实际又要选择哪种呢?
说说我目前 react 所选的操作,tailwind(原子类)+ CSS modules,写一些小项目或者 demo 甚至都没必要写 css 代码,毕竟 css 是大多数前端程序员都不是那么想写的(包括我)。而做一些自定义的小组件的话那肯定是 styled-components,而 styled-jsx,对组件代码牺牲挺大所以不怎么写。
不过每个人使用风格不同,我一开始接触原子类是 windicss,用久了之后习惯了常用的 class,编写起来可以说是相当的快捷了。
不过相比 Vue 而言,react 的 css 实现着实费劲。
参考链接:
CSS Modules 用法教程 - 阮一峰的网络日志 (ruanyifeng.com)