写CSS的方式
整个前端已经是组件化的天下,而CSS的设计就不是为组件化而生的,所以在目前组件化的框架中都在需要一种合适的CSS解决方案
-
事实上
CSS一直是React的痛点,React官方并没有给出在React中统一的样式风格 -
因此从普通的
CSS,到css modules,再到css in js,有几十种不同的解决方案,上百个不同的库 -
大家一致在寻找最好的或者说最适合自己的
CSS方案
内联样式
内联样式是官方推荐的一种css样式的写法,style 接受一个采用小驼峰命名属性的 JavaScript 对象,,而不是 CSS 字符串,并且可以引用state中的状态来设置相关的样式
- 优点:
-
样式之间不会有冲突
-
可以动态获取当前
state中的状态
-
- 缺点:
-
写法上都需要使用驼峰标识
-
某些样式没有提示
-
大量的样式,代码混乱
-
某些样式无法编写(比如伪类/伪元素)
-
CSS文件
-
普通的
css通常会编写到一个单独的文件再进行引入 -
组件化开发中总是希望组件是一个独立的模块,即便是样式也只是在自己内部生效,不会相互影响
-
但是普通的css都属于全局的css,样式之间会相互影响,这种编写方式最大的问题是样式之间会相互层叠掉
-
配置
less使用: 这里要配置less环境,ant-design文档有相关配置-
需要先安装
craco这个工具:npm install @craco/craco -
package.json中scripts的react-script改为craco -
创建
craco.config.js -
引入
craco-less来帮助加载less样式和修改变量 -
在
craco.config.js写配置const CracoLessPlugin = require("craco-less"); module.exports = { plugins: [ { plugin: CracoLessPlugin, options: { lessLoaderOptions: { lessOptions: { modifyVars: { "@primary-color": "#1DA57A" }, javascriptEnabled: true, }, }, }, }, ], };
-
css modules
css modules并不是React特有的解决方案,而是所有使用了类似于webpack配置的环境下都可以使用的需要配置webpack.config.js中的modules: true等
-
React的脚手架已经内置了css modules的配置,.css/.less/.scss等样式文件都需要修改成.module.css/.module.less/.module.scss等,就可以引用并且进行使用了 -
css modules确实解决了局部作用域的问题,也是很多人喜欢在React中使用的一种方案 -
缺点:
-
引用的类名,不能使用连接符(
.home-title),在JavaScript中是不识别的 -
所有的
className都必须使用{style.className}的形式来编写 -
不方便动态来修改某些样式,依然需要使用内联样式的方式
-
CSS in JS
官方文档也有提到过CSS in JS这种方案,CSS-in-JS是指一种模式,其中 CSS 由 JavaScript 生成而不是在外部文件中定义
-
此功能并不是
React的一部分,而是由第三方库提供,React对样式如何定义并没有明确态度 -
CSS-in-JS的模式就是一种将样式(CSS)也写入到JavaScript中的方式,并且可以方便的使用JavaScript的状态,所以React又被人称之为All in JS -
这种开发的方式也受到了很多的批评:
Stop using CSS in JavaScript for web development -
虽然有批评声音,但不可否认这种方式很强大方便,目前可以说
CSS-in-JS是React编写CSS最为受欢迎的一种解决方案 -
CSS-in-JS通过JavaScript来为CSS赋予一些能力,包括类似于CSS预处理器一样的样式嵌套、函数定义、逻辑复用、动态修 改状态等等
styled-components
目前比较流行的CSS-in-JS的库有哪些呢? styled-components、emotion、glamorous,目前可以说styled-components依然是社区最流行的CSS-in-JS库
-
安装
styled-components:npm install styled-components -
在
styled component中,是通过下面方法解析模块字符串,最终生成想要的样式-
ES6中增加了模板字符串的语法,模板字符串还有另外一种用法:标签模板字符串(Tagged Template Literals) -
调用函数的时候可以使用
(),也可以使用``,使用``并插入其他变量时,模板字符串会被拆分了,第一个元素是数组,是被模块字符串拆分的字符串组合,后面的元素是一个个模块字符串传入的内容function foo(...args) { console.log(args); } foo("hello world"); // ['hello world'] foo`hello world`; // ['hello world'] const name = "template"; foo`hello ${name}`; // 会拆分 [['hello ', ''], 'template']
-
基本使用
styled-components的本质是通过函数的调用,最终创建出一个组件,这个组件会被自动添加上一个不重复的class,styled-components会给该class添加相关的样式
-
它支持类似于
CSS预处理器一样的样式嵌套 -
支持直接子代选择器或后代选择器,并且直接编写样式
-
可以通过&符号获取当前元素
-
直接伪类选择器、伪元素等
props、attrs属性
-
props可以被传递给styled组件:<AppWrapper link_color="green"></AppWrapper> -
使用传入的值:
color: ${props=> props.link_color}; -
attrs可以修改或设置默认值:export const AppWrapper = styled.div.attrs((props)=> ({ link_color: props.link_color || 'red' }))``
高级特性
-
支持样式继承:
export const ButtonWrapper = styled(AppWrapper)`` -
可以设置主题:
classnames
-
React在JSX给了开发者足够多的灵活性,可以像编写JavaScript代码一样,通过一些逻辑来决定是否添加某些class -
也可以借助第三方库实现动态添加
class:先安装npm install classnames,再引入import classNames from 'classnames'
练习代码
// style/comCss.module.css
.btn {
background: rgb(232, 203, 129);
padding: 8px 18px;
font-size: 24px;
color: cadetblue;
border: 2px solid salmon;
border-radius: 8px;
/* 这里要配置less环境,ant-design文档有相关配置,
1. 需要先安装craco这个工具
2. package.json中scripts的react-script改为craco
3. 创建craco.config.js
4. 引入craco-less 来帮助加载 less 样式和修改变量
5. 在craco.config.js写配置
*/
.link {
color: cornflowerblue;
text-decoration: underline;
&:hover {
color: blue;
}
}
}
// style/comCss.js
import styled from "styled-components";
// 基础用法
/* export const AppWrapper = styled.div`
.btn {
padding: 8px;
background: bisque;
border: 2px solid gainsboro;
border-radius: 8px;
.link {
color: red;
&:hover {
color: pink;
}
}
}
`; */
const Button = styled.button` // 这里渲染成button,styled.xxxx 这里就会渲染成xxxx
padding: 8px;
background: bisque;
border: 2px solid gainsboro;
border-radius: 8px;
`
export const ButtonWrapper = styled(Button)` // 继承
color: olive;
font-size: large;
`
export const AppWrapper = styled.div.attrs((props)=> ({
// 使用驼峰命名会报错
link_color: props.link_color || 'red'
}))`
background: yellowgreen;
.btn {
padding: 8px;
background: bisque;
border: 2px solid gainsboro;
border-radius: 8px;
.link {
color: ${props=> props.link_color};
&:hover {
color: pink;
}
}
}
.active {
color: powderblue;
}
`
// ComCss.jsx
import React, { PureComponent } from "react";
import comCss from "../style/comCss.module.css";
import { AppWrapper, ButtonWrapper } from "../style/comCss";
import classNames from "classnames";
export class ComCss extends PureComponent {
constructor() {
super();
this.state = {
isActive: true,
};
}
render() {
return (
<div>
<h3>ComCss-react中写css的方式</h3>
{/* 第一种直接写内联样式 */}
<span style={{ color: "red" }}>内联样式</span>
{/* 第二种使用className,再引入css文件 */}
{/* 第三种使用模块化和配置less具体操作看style/comCss.module.css */}
<button className={comCss.btn}>
按钮
<span className={comCss.link}>啦啦啦啦</span>
</button>
{/*
第四种 css in js方式
先install styled-components
link_color不传会使用默认值
*/}
<AppWrapper link_color="green">
<button className="btn">
按钮
<span className="link">啦啦啦啦</span>
</button>
</AppWrapper>
<ButtonWrapper>继承样式按钮</ButtonWrapper>
{/* classNames库的使用,先install classnames,在引入import classNames from 'classnames' */}
<AppWrapper>
<button
className={classNames(["btn", { active: this.state.isActive }])}
>
classnames库的使用示例按钮
</button>
</AppWrapper>
</div>
);
}
}
export default ComCss;
react-transition-group
在开发中想要给一个组件的显示和消失添加某种过渡动画,可以很好的增加用户体验;可以通过原生的CSS来实现这些过渡动画,但是React社区提供了react-transition-group用来完成过渡动画
-
React曾为开发者提供过动画插件react-addons-css-transition-group,后由社区维护,形成了现在的react-transitiongroup -
react-transition-group本身非常小,不会为应用程序增加过多的负担 -
安装:
npm install react-transition-group --save -
react-transition-group主要学习下面要常用的三个组件
CSSTransition
-
执行过程中有三个状态:
appear、enter、exit,三种状态需要定义对应的CSS样式:-
开始状态:对应的类是
-appear、-enter、exit -
执行动画:对应的类是
-appear-active、-enter-active、-exit-active -
执行结束:对应的类是
-appear-done、-enter-done、-exit-done
-
-
CSSTransition常见对应的属性:-
in:触发进入或者退出状态如果添加了
unmountOnExit={true},那么该组件会在执行退出动画结束后被移除掉当
in为true时,触发进入状态,会添加-enter、-enter-acitve的class开始执行动画当动画执行结束后,会移除两个
class,并且添加-enter-done的class当
in为false时,触发退出状态,会添加-exit、-exit-active的class开始执行动画当动画执行结束后,会移除两个
class,并且添加-enter-done的class -
classNames:决定了在编写css时对应的class名称:比如card-enter、card-enter-active、card-enter-done -
timeout:过渡动画的时间 -
appear:是否在初次进入添加动画(需要和in同时为true) -
unmountOnExit:退出后卸载组件 -
其他属性可以参考官网学习:reactcommunity.org/react-trans…
-
-
CSSTransition对应的钩子函数:主要为了检测动画的执行过程,来完成一些JavaScript的操作-
onEnter:在进入动画之前被触发 -
onEntering:在应用进入动画时被触发 -
onEntered:在应用进入动画结束后被触发
-
SwitchTransition
SwitchTransition可以完成两个组件之间切换的炫酷动画,比如有一个按钮需要在on和off之间切换,希望看到on先从左侧退出,off再从右侧进入
-
SwitchTransition中主要有一个属性:mode='in-out':表示新组件先进入,旧组件再移出,mode='out-in':表示旧组件先移出,新组件再进入 -
如何使用
SwitchTransition呢?-
SwitchTransition组件里面要有CSSTransition或者Transition组件,不能直接包裹你想要切换的组件 -
SwitchTransition里面的CSSTransition或Transition组件不再像以前那样接受in属性来判断元素是何种状态,取而代之的是key属性 -
代码参考下面的练习代码
-
TransitionGroup
当有一组动画时,需要将这些CSSTransition放入到一个TransitionGroup中来完成动画
练习代码
import React, { PureComponent, createRef } from 'react'
import { CSSTransition, SwitchTransition, TransitionGroup } from 'react-transition-group'
import { TransitionWrapper, } from '../style/comTransite'
export class ComTransite extends PureComponent {
constructor() {
super()
this.state = {
isCssTrs: false,
groupList: [
'哈哈哈哈',
'嘻嘻嘻嘻',
'哒哒哒哒',
'啦啦啦啦',
]
}
this.nodeRef = createRef()
}
changIsCssTrs() {
this.setState({
isCssTrs: !this.state.isCssTrs
})
}
delGroupItem(index) {
const list = [...this.state.groupList]
list.splice(index, 1)
this.setState({
groupList: list
})
}
addGroupItm() {
let list = [...this.state.groupList]
list.push('嘿嘿嘿嘿')
this.setState({
groupList: list
})
this.groupRef = createRef()
}
render() {
return (
<div>
<h3>ComTransite-过渡动画</h3>
{/* 先install react-transition-group */}
<button onClick={e=> this.changIsCssTrs()}>执行动画</button>
<TransitionWrapper>
{/* appear
默认情况下,子组件在首次安装时不会执行 Enter 转换,无论 的值如何in。如果您想要此行为,请将appear和 都设置in为true。
unmountOnExit
默认情况下,子组件在达到该'exited'状态后保持安装状态。unmountOnExit如果您希望在组件退出后卸载该组件,请进行设置。
*/}
{/* CSSTransition 显示隐藏时可以增加组件的进入和退出动画 */}
<h3>CSSTransition组件显示隐藏</h3>
<CSSTransition
appear
unmountOnExit={true}
in={this.state.isCssTrs}
nodeRef={this.nodeRef}
classNames='cssTrs'
timeout={2000}
onEnter={e=>console.log('进入动画开始了')}
onEntering={e=>console.log('进入动画执行中')}
onEntered={e=>console.log('进入动画执行结束')}
onExit={e=>console.log('结束动画开始了')}
onExiting={e=>console.log('结束动画执行中')}
onExited={e=>console.log('结束动画执行结束')}
>
<div ref={this.nodeRef}> CSSTransition动画 </div>
</CSSTransition>
</TransitionWrapper>
<TransitionWrapper>
{/* 可以完成两个组件之间切换的炫酷动画,当您想要控制状态转换之间的渲染时可以使用它 */}
<h3>SwitchTransition组件之间切换</h3>
<SwitchTransition mode='out-in'>
{/* in时判断显示隐藏不能设置,这里设置key根据key判断执行动画 */}
<CSSTransition
key={this.state.isCssTrs ? "exit": "login"}
classNames="login"
timeout={1000}
>
<button onClick={e => this.setState({ isCssTrs: !this.state.isCssTrs })}>
{ this.state.isCssTrs ? "退出": "登录" }
</button>
</CSSTransition>
</SwitchTransition>
</TransitionWrapper>
<TransitionWrapper>
<h3>TransitionGroup管理一组组件随时间的安装和卸载</h3>
{/* 默认渲染div,可以通过component修改 */}
<TransitionGroup component="div">
{
this.state.groupList.map((m,mi)=> {
// 这里注意新增的元素和已有的绑定的key值是一致时,页面不会渲染也不会报错只会展示一个,若想重新可以把mi换成m
return <CSSTransition
key={mi}
nodeRef={this.groupRef}
classNames="group"
timeout={1000}
>
<p ref={this.groupRef}>
{m}
<button onClick={e=>this.delGroupItem(mi)}>删除</button>
</p>
</CSSTransition>
})
}
</TransitionGroup>
</TransitionWrapper>
<button onClick={e=>this.addGroupItm()}>addGroupItem</button>
</div>
)
}
}
export default ComTransite
// comTransite.js
import { styled } from "styled-components";
export const TransitionWrapper = styled.div`
.cssTrs-enter {
opacity: 0;
}
.cssTrs-enter-active {
opacity: 1;
transition: opacity 2s ease;
}
.cssTrs-exit {
opacity: 1;
}
.cssTrs-exit-active {
opacity: 0;
transition: opacity 2s ease;
}
.login-enter {
opacity: 0;
transform: translateX(-200px);
}
.login-enter-active {
opacity: 1;
transform: translateX(0px);
transition: all 1s ease;
}
.login-exit {
opacity: 1;
transform: translateX(0px);
}
.login-exit-active {
opacity: 0;
transform: translateX(200px);
transition: all 1s ease;
}
.group-enter {
transform: translateX(150px);
opacity: 0;
}
.group-enter-active {
transform: translateX(0);
opacity: 1;
transition: all 1s ease;
}
.group-exit {
transform: translateX(0);
opacity: 1;
}
.group-exit-active {
transform: translateX(150px);
opacity: 0;
transition: all 1s ease;
}
`