我正在参加「掘金·启航计划」
目前前端已经是组件化的天下,然而CSS的设计本就不是为组件化而设计的,目前组件化框架中都需要一种合适的CSS解决方案。在组件化中选择合适的CSS解决方案应该符合以下条件:
可以编写局部的css(具备自己的作用域,不会污染其他组件的内部元素);
可以编写动态的css(可以获取当前组件的状态,根据不同状态来生成不同的css);
支持所有css的特性(如:伪类、动画、媒体查询等等);
...
事实上,CSS一直是React中的痛点,在这一点上,Vue要好于React:
- 比如.vue中可通过在标签中编写自己的样式
- 可通过是否添加scoped来决定编写的样式是全局有效还是局部有效
- 可通过lang来指定使用哪种css预处理器(如:less、sass)
- 可通过内联样式风格的方式来根据最新状态设置和改变css
...
相比而言,React官方并没有给出React中统一的样式风格
(由此,从普通的css,到css modules,再到css in js,有几十种不同的解决方案,上百个不同的库。 大家一直在寻找最好的或者说最适合自己的CSS方案,但是到目前为止也没有统一的方案)
方式一:内联样式
该方式是官方推荐的一种写法:
- style接收一个采用驼峰形式命名属性的JS对象
- 可以通过引用state中的状态来设置相关的样式
优点
- 内联样式之间不会有冲突
- 可以动态获取当前state中的状态
缺点
- 写法上都需要以驼峰的形式来指定属性名
- 某些样式在编译器中不会有提示
- 大量的样式会造成代码比较混乱
- 某些样式无法通过此方式编写:比如伪类、伪元素
import React, { PureComponent } from 'react'
export class App extends PureComponent {
constructor() {
super()
this.state = {
titleSize: 30
}
}
addTitleSize() {
this.setState({ titleSize: this.state.titleSize + 2 })
}
render() {
const { titleSize } = this.state
return (
<div>
<button onClick={e => this.addTitleSize()}>增加titleSize</button>
<h2 style={{color: "red", fontSize: `${titleSize}px`}}>我是标题</h2>
<p style={{color: "blue", fontSize: "20px"}}>我是内容, 哈哈哈</p>
</div>
)
}
}
export default App
方式二:引入css文件
将css编写到css文件中,然后通过import引入
但是组件化开发中希望组件是一个个独立的模块,这样引入的css是属于全局的,样式之间会互相影响
这样编写最大的问题就是样式之间会相互层叠掉
方式三:css modules
css moudles并不是React特有的方案,而是所有使用了webpack配置的环境下都可以使用
如果在其他框架搭建的项目中使用,需要进行配置
- 比如将webpack.config.js中的moudles配置成true
React的脚手架内置了css modules 的配置
- 只需将.css .less .scss等文件命名为
.module.css.module.less.module.scss
示例
比如,创建一个App.module.css文件编写相应的样式:
.title {
font-size: 32px;
color: green;
}
.content {
font-size: 22px;
color: orange;
}
在App.jsx中引入并使用:
import React, { PureComponent } from 'react'
import appStyle from "./App.module.css"
export class App extends PureComponent {
render() {
return (
<div>
<h2 className={appStyle.title}>我是标题</h2>
<p className={appStyle.content}>我是内容</p>
</div>
)
}
}
export default App
需要注意的是,这种方案也有自己的缺点:
- 引用的类名不能以短横线链接 比如 .content-title,这种在JS中是不识别的
- 所有的className都必须使用
className={styleModule.clssName}的形式来编写 - 不方便动态来修改样式,如果想要动态修改依然要使用内联样式的方式
方式四:在React中使用css预处理器(如less)
如果想在React中编写less/scss,需要进行额外的配置,这里以less为例来进行讲解。
学习过webpack的同学知道,如果在基于webpack构建的脚手架中使用less,需要使用less-loader、css-loader、style-loader来对less文件进行处理
然而,对于React脚手架来说,虽是基于webpack脚手架进行构建的,但该脚手架并未对less/scss进行相关配置。
而且正常情况下我们是看不到webpack配置文件的(需要通过npm run reject对webapck配置文件进行暴露,且该操作是不可逆的,且修改起来有些难度,需要阅读源码,而且容易修改错误),那么如何在React中使用less呢?
此时可以使用craco工具
目前该工具在React开发中使用的非常多,利用该工具可以实现在脚手架构建项目的基础上进行webpack的配置
使用方法
- 安装craco
npm install @craco/craco
如果遇到安装版本与react新版本不适配的问题,可安装@craco/craco@alpha
- 安装craco-less工具
用于加载less样式,以及修改一些变量
npm install craco-less
如果遇到安装版本与react新版本不适配问题,可安装 craco-less@alpha
- 在根目录下创建craco.config.js文件
用于进行相关配置
const CracoLessPlugin = require('craco-less');
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: { '@primary-color': '#1DA57A' },
javascriptEnabled: true,
},
},
},
},
],
};
- 修改start启动命令,原脚手架中启动命令为"react-scripts start"现需修改为"craco start"
此时需要craco帮我们启动项目,当craco帮我们启动项目时,会自动将craco.config.js中的配置信息与原脚手架中webpack中的配置信息进行合并
- 此时便可以在项目中引入less文件并生效了
App.less
@primaryColor: pink;
.section {
border: 1px solid @primaryColor;
.title {
font-size: 30px;
color: @primaryColor;
}
.content {
font-size: 20px;
color: @primaryColor;
}
}
App.jsx
import React, { PureComponent } from 'react'
import "./App.less"
export class App extends PureComponent {
render() {
return (
<div className='app'>
<div className='section'>
<h2 className='title'>我是标题</h2>
<p className='content'>我是内容</p>
</div>
</div>
)
}
}
export default App
方式五:CSS in JS 之 styled-components
认识CSS in JS
CSS in JS 是指一种模式,其中CSS由JavaScript生成而不是在外部文件定义;这种方式也不是React的一部分,是由第三方库所提供的。
在传统前端开发中,会将HTML、CSS、JavaScript进行分离
而在React开发思想中,认为逻辑本身就和UI是无法分离的,所以才会有了JSX语,而样式也是UI的一部分。
CSS in JS 模式便是一种将CSS写入到JS中的方式,并且可以方便的使用JS中的状态(数据)。
所以React也会被人称之为
All in JS
优点
通过JS来为CSS赋予能力,可以编写包括CSS预处理器一样的样式嵌套、函数定义、逻辑复用、动态修改状态等;也是React编写CSS最受欢迎的一种解决方案。
目前比较流行的CSS in JS 库主要包括:
styled-components、emotion、glamorous
其中,styled-components 是社区最流行的
styled-components使用方法
安装
npm install styled-components
基本使用
styled-components本质是通过函数的调用,最终创建出一个组件:
这个组件会被自动添加上一个不重复的class
styled-components会给该class添加相关的样式
styled-components中的函数调用采用的是ES6中的模板字符串的语法
- 在styled.js中通过styled-components创建一个包含指定样式的div
import styled from "styled-components";
export const HomeWrapper = styled.div`
.home {
background-color: green;
}
`;
ps:可以安装vscode-styled-components
此时在模板字符串中的样式代码便可以高亮显示
- 在App.jsx中引入创建好的div并使用
import React, { PureComponent } from 'react'
import { HomeWrapper } from './style'
export class App extends PureComponent {
render() {
return (
<div>
<HomeWrapper>
<div className="home">123</div>
</HomeWrapper>
</div>
)
}
}
export default App
需要注意的是,想要使用HomeWrapper中所编写的样式的话需要在元素上通过className指定
styled-components在创建组件时也支持类似于CSS预处理器一样的嵌套写法
比如通过
&符号获取当前元素
export const HomeWrapper = styled.div`
.home {
background-color: green;
&:hover {
background-color: red;
}
}
`;
也可以方便的使用抽取的公共属性值
比如在style/variables.js中定义了主题颜色themeColor
export const themeColor = "#0089ff";
然后在创建样式组件时便可以引入并使用
import styled from "styled-components";
import { themeColor } from "./style/variables";
export const HomeWrapper = styled.div`
.home {
background-color: ${themeColor};
}
`;
props的使用
如果styled-components中的样式不希望写死,而是希望通过外部传递的话可以直接将传递的数据写在创建好的样式组件上,此时在组件内部便可以通过props接收:
需要注意的是,props的使用要写成回调函数的形式,不要直接通过props.xx的方式使用,这是因为:
- 如果直接通过props.xx的方式使用,会直接去上层作用域去寻找props(此时是找不到的)
写成回调函数的形式后,组件内部会去调用该函数然后将内部的props传递进来,从而实现获取外部传递进来的数据
使用这种方式也可以很方便实现样式的动态修改:
import React, { PureComponent } from 'react'
import { HomeWrapper } from './style'
export class App extends PureComponent {
constructor() {
super()
this.state = {
size: '20px'
}
}
render() {
const {size} = this.state
return (
<div>
<HomeWrapper color="orange" size={size}>
<div className="home">123</div>
<button onClick={e => this.setState({size: '16px'})}>修改字体大小</button>
</HomeWrapper>
</div>
)
}
}
export default App
import styled from "styled-components";
export const HomeWrapper = styled.div`
.home {
background-color: ${(props) => props.color};
font-size: ${(props) => props.size};
&:hover {
background-color: red;
}
}
`;
attrs的使用
如果样式组件内部需要使用传递的props中的数据中的color,但是别人在使用样式组件时并没有传递进来color,此时内部获取到的将是undefined,这样样式便无法达到预期的效果了,为了避免这种情况的方式,我们可以使用styled-components提供的attrs方法:
可以向该方法中传递一个用于处理props的回调函数
该方法会返回一个新的方法,然后通过模板字符串语法去调用该方法即可
export const HomeWrapper = styled.div.attrs((props) => ({
newColor: props.color || "pink",
}))`
.home {
background-color: ${(props) => props.newColor};
}
`;
此时外部在使用HomeWrapper时,如果传递了color,那么将会使用传递的color;如果没有传递color,那么将会使用默认值"pink"
ThemeProvider的使用
styled-components中提供了ThemeProvider
可通过ThemeProvider去共享数据
需要共享的数据通过theme属性去传递
在创建样式组件时可通过props.theme.xx来获取共享的数据
- 引入ThemeProvider,并在根组件外侧进行包裹,并通过theme传递数据
- 创建样式组件时便可以通过props.theme.xx来获取共享的数据
组件样式继承
styled-components中支持组件样式的继承,可以很方便的来抽取多个组件的公共样式
import styled from "styled-components";
const CommonButton = styled.button`
padding: 8px 10px;
border-radius: 5px;
border: none;
`;
export const SuccessButton = styled(CommonButton)`
background-color: #67c23a;
color: #fff;
`;
import React, { PureComponent } from 'react'
import { SuccessButton } from './style'
export class App extends PureComponent {
render() {
return (
<div>
<SuccessButton>我是success button</SuccessButton>
</div>
)
}
}
export default App
方式六:使用classnames
在React的JSX中添加class的时候,可以通过JS逻辑来决定是否添加某个class:
我们也可以借助classnames库来更加方便的实现动态添加class:
基本使用方式
安装
npm install classnames
import React, { PureComponent } from 'react'
import classNames from 'classnames'
export class App extends PureComponent {
constructor() {
super()
this.state = {
isbbb: true,
isccc: true
}
}
render() {
const { isbbb, isccc } = this.state
const classList = ["aaa"]
if (isbbb) classList.push("bbb")
if (isccc) classList.push("ccc")
const classname = classList.join(" ")
return (
<div>
<h2 className={`aaa ${isbbb ? 'bbb': ''} ${isccc ? 'ccc': ''}`}>aaa</h2>
<h2 className={classname}>bbb</h2>
{/* 使用classNames */}
<h2 className={classNames("aaa", { bbb:isbbb, ccc:isccc })}>ccc</h2>
<h2 className={classNames(["aaa", { bbb: isbbb, ccc: isccc }])}>ddd</h2>
</div>
)
}
}
export default App