- 用例中的干净的脚手架的创建可以参考另一篇文章:3.React 组件化开发
- React官方并没有给出在React中统一的样式风格:
- 由此,从普通的css,到css modules,再到css in js,有几十种不同的解决方案,上百个不同的库;
- 大家一致在寻找最好的或者说最适合自己的CSS方案,但是到目前为止也没有统一的方案;
一、内联样式
-
内联样式是官方推荐的一种css样式的写法:
- style 接受一个采用小驼峰命名属性的 JavaScript 对象,,而不是 CSS 字符串;
- 并且可以引用state中的状态来设置相关的样式;
-
内联样式的优点:
- 1.内联样式, 样式之间不会有冲突
- 2.可以动态获取当前state中的状态
-
内联样式的缺点:
- 1.写法上都需要使用驼峰标识
- 2.某些样式没有提示
- 3.大量的样式, 代码混乱
- 4.某些样式无法编写(比如伪类/伪元素)
-
所以官方依然是希望内联样式和普通的css来结合编写;
import React, { Component } from 'react'; export class App extends Component { constructor() { super(); this.state = { fontSize: 25, }; } render() { const { fontSize } = this.state; return ( <div> <p style={{fontSize: '20px', color: 'red',backgroundColor: 'yellow'}}> 内连样式 </p> <p style={{fontSize: fontSize + 'px', color: 'blue'}}> 内连样式2 </p> </div> ); } } export default App;
二、CSS 文件
- 普通的css我们通常会编写到一个单独的文件,之后再进行引入。
- 这样的编写方式和普通的网页开发中编写方式是一致的:
- 如果我们按照普通的网页标准去编写,那么也不会有太大的问题;
- 但是组件化开发中我们总是希望组件是一个独立的模块,即便是样式也只是在自己内部生效,不会相互影响;
- 但是普通的css都属于全局的css,样式之间会相互影响;
- 这种编写方式最大的问题是样式之间会相互层叠掉;
-
创建一个干净的脚手架
-
父组件 App.js(App.jsx) 内容如下
import React, { Component } from 'react' // 引入对应的css样式 import "./App.css" export default class App extends Component { render() { return ( <div> <h3 className="title">我是标题</h3> <p className="content">我是内容我是内容我是内容我是内容我是内容</p> </div> ) } }
-
创建一个父组件对应的 App.css 文件
.title { font-size: 25px; margin: 0; } .content { font-size: 15px; line-height: 1.5; }
-
如果有两个子组件 Child1.jsx,Child2.jsx,其中Child2.jsx, Child1.jsx对应有自己的css文件,Child2.css, Child1.css , 这两个css文件中相同css类名之间会相互影响,即css 文件之间并不是只对其对应的模块有效
三、Less 配置和编写
【1】create-react-app 创建的项目
- 我们需要修改 create-react-app 的默认配置,这里我们使用 craco (一个对 create-react-app 进行自定义配置的社区解决方案)。
- 编写的Less 文件生成的css,都属于全局的css,样式之间会相互影响;
- 针对 less 的使用,可以参考 https://4x-ant-design.antgroup.com/docs/react/use-with-create-react-app-cn/
-
安装 craco
npm install @craco/craco
-
安装 craco-less
npm install craco-less
-
在项目的根目录下创建 craco.config.js 文件,作为 react 的配置文件
const CracoLessPlugin = require('craco-less'); module.exports = { plugins: [ { plugin: CracoLessPlugin, }, ], };
-
修改 package.json 文件中的项目启动项
"scripts": { "start": "craco start", "build": "craco build", "test": "craco test", "eject": "react-scripts eject" },
-
启动项目
npm run start
【2】vite 创建的项目
- 编写的Less 文件生成的css,都属于全局的css,样式之间会相互影响;
-
在项目中安装 less 和 less-loader,它们是处理 Less 文件所需的依赖项:
npm install less less-loader --save-dev
-
修改 vite.config.js 文件,添加对 Less 的支持。
export default defineConfig({ plugins: [react()], css: { preprocessorOptions: { less: { // 可选:如果你需要全局变量或混入文件,可以在这里配置 //additionalData: `@import "./src/styles/global.less";`, }, }, }, });
-
在 src 目录下创建一个 .less 文件,例如 src/App.less。
@primary-color: #4caf50; .app { color: @primary-color; font-size: 1.5em; }
-
在src/App.jsx 组件中引入该 Less 文件。
import React, { memo } from 'react'; import './App.less'; const App = memo(() => { return <div className='app'>App</div>; }); export default App;
四、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}
的形式来编写; - 不方便动态来修改某些样式,依然需要使用内联样式的方式;
- 创建一个干净的脚手架
- 父组件 App.js(App.jsx) 内容如下
import React, { Component } from 'react'; // 引入样式 import appStyle from './App.module.css'; export default class App extends Component { render() { return ( <div> {/* 使用样式 */} <div className={appStyle.title}>标题</div> <p className={appStyle.content}> 我是内我是内容我是内容我是内容我是内容我是内容容 </p> </div> ); } }
- 新建一个 App.module.css 文件,内容如下
.title { font-size: 25px; color: red; } .content { font-size: 15px; line-height: 1.2; }
五、CSS in JS
- 官方文档也有提到过CSS in JS这种方案:
- “CSS-in-JS” 是指一种模式,其中 CSS 由 JavaScript 生成而不是在外部文件中定义;
- 注意此功能并不是 React 的一部分,而是由第三方库提供;
- React 对样式如何定义并没有明确态度;
- 在传统的前端开发中,我们通常会将结构(HTML)、样式(CSS)、逻辑(JavaScript)进行分离。
- 但是在前面的学习中,我们就提到过,React的思想中认为逻辑本身和UI是无法分离的,所以才会有了JSX的语法。
- 样式呢?样式也是属于UI的一部分;
- 事实上CSS-in-JS的模式就是一种将样式(CSS)也写入到JavaScript中的方式,并且可以方便的使用JavaScript的状态;
- 所以React有被人称之为 All in JS;
- 目前比较流行的CSS-in-JS的库有哪些呢?
- styled-components
- emotion
- glamorous
- 目前可以说styled-components依然是社区最流行的CSS-in-JS库
【1】 使用 styled-components
- styled-components的本质是通过函数的调用,最终创建出一个组件:
- 更多用法可以参考官网:https://styled-components.com/docs
-
安装 styled-components 库
npm install styled-components
-
在 vscode 中安装插件 vscode-styled-components
-
创建一个干净的脚手架
-
父组件 App.js(App.jsx) 内容如下
import React, { Component } from 'react'; import { AppWrapper, ContentWrapper, FooterWrapper } from './Style.js'; export default class App extends Component { constructor() { super(); this.state = { color: '#999', contentStyle: { fontSize: '14' }, }; } changeContentColor() { this.setState({ color: '#333', }); } render() { const { contentStyle, color } = this.state; return ( <AppWrapper> <h2 className="title"> <p>标题</p> <small>小标题</small> </h2> {/* 通过结构传递 props */} <ContentWrapper color={color} {...contentStyle}> <p>内容</p> <ul> <li>列表1</li> <li>列表2</li> <li>列表3</li> <li>列表4</li> </ul> <button onClick={(e) => this.changeContentColor()}> 修改内容的颜色 </button> </ContentWrapper> <FooterWrapper>底部</FooterWrapper> </AppWrapper> ); } }
-
新建 Style.js 文件,用于设置样式
import styled from 'styled-components'; // 用法参考:函数模板字符串 // styled.div 是一个函数,返回一个组件 export const AppWrapper = styled.div` .title { color: red; p { font-size: 20px; } small { font-size: 12px; } } `; // 通过 props 传递参数 export const ContentWrapper = styled.div` p { color: ${(props) => props.color}; } ul { li { color: ${(props) => props.color}; font-size: ${(props) => props.fontSize}px; &:hover { color: red; } cursor: pointer; } } `; // 设置默认值 export const FooterWrapper = styled.div.attrs((props) => ({ color: props.color || 'yellow', }))` color: ${(props) => props.color}; `;
六、React 中添加 class
【1】classnames 库
-
git 仓库:https://github.com/JedWatson/classnames
-
classnames 是一个用于动态地构建 CSS 类名字符串的 JavaScript 库,常用于 React 项目中。它可以根据条件来组合多个类名,简化了在模板中根据逻辑添加或删除 CSS 类名的过程。
-
不使用 classnames 库的代码
<div className={`button ${isActive ? 'button-active' : ''} ${isDisabled ? 'button-disabled' : ''}`}> Click me </div>
-
使用 classnames 库的代码
import classNames from 'classnames'; <div className={classNames('button', { 'button-active': isActive, 'button-disabled': isDisabled })}> Click me </div>
-
创建一个干净的脚手架
-
安装 classnames 库
npm install classnames
-
父组件 App.js(App.jsx) 内容如下
import React, { Component } from 'react'; import classNames from 'classnames'; export default class App extends Component { render() { //结果:"class1 class2" const classname1 = classNames('class1', 'class2'); //结果:"class1 class2" const classname2 = classNames('class1', { class2: true }); //结果:"class1 class2" const classname3 = classNames('class1', { class2: 1 }); //结果:"class1" const classname4 = classNames('class1', { class2: false }); //结果:"class1" const classname5 = classNames({ class1: true, class2: false }); //结果:"class1 class2" const classname6 = classNames(['class1', 'class2']); //结果:class1 class3 class4 const classname7 = classNames('class1', { class2: false }, [ 'class3', 'class4', ]); return ( <div> <div className={classname1}>classname1</div> <div className={classname2}>classname2</div> <div className={classname3}>classname3</div> <div className={classname4}>classname4</div> <div className={classname5}>classname5</div> <div className={classname6}>classname6</div> <div className={classname7}>classname7</div> </div> ); } }
七、React 的过渡动画
(一) react-transition-group 介绍
-
在开发中,我们想要给一个组件的显示和消失添加某种过渡动画,可以很好的增加用户体验。
-
当然,我们可以通过原生的CSS来实现这些过渡动画,但是React社区为我们提供了react-transition-group用来完成过渡动画。
-
React曾为开发者提供过动画插件 react-addons-css-transition-group,后由社区维护,形成了现在的 react-transition-
group。 -
这个库可以帮助我们方便的实现组件的 入场 和 离场 动画,使用时需要进行额外的安装:
npm install react-transition-group --save
-
react-transition-group本身非常小,不会为我们应用程序增加过多的负担。
-
github 地址:https://github.com/reactjs/react-transition-group
-
官方网站:https://reactcommunity.org/react-transition-group/
(二) react-transition-group主要组件
【1】Transition 组件
-
该组件是一个和平台无关的组件(不一定要结合CSS);如果需要结合 CSS 最好使用 CSSTransition,它继承了Transition的所有特性,而且包含了一些额外的特性。
-
Transition组件允许您使用简单的声明式 API描述随时间从一个组件状态到另一个组件状态的转换
-
默认情况下,Transition组件不会改变它呈现的组件的行为,它只跟踪组件的进入和退出状态。赋予这些状态以意义和效果取决于您
-
转换可以处于4 种主要状态
- entering 进入中
- entered 进入后
- exiting 离开中
- exited 离开后
-
过渡状态通过
in
属性切换。当为 true 时组件开始进入阶段。在此阶段,组件将从其当前的过渡状态转移到entering
过渡期间,然后在entered
完成后进入该阶段 -
in改为false进行同样的事情,状态从移动
exiting
到exited
-
属性:
属性名 类型 默认 含义 nodeRef element ReactDOM.findDOMNode 对需要转换的 DOM 元素的 React 引用 in boolean false 显示组件;触发进入或退出状态 children Function或element 必需 function可以使用子元素代替 React 元素。此函数使用当前转换状态(entering, entered, exiting,exited)调用,可用于将特定于上下文的属性应用于组件 timeout number 无 过渡的持续时间,以毫秒为单位
-
创建一个干净的脚手架
-
安装 react-transition-group
npm install react-transition-group --save
-
修改 src/App.js(App.jsx) 文件,引入Transition 组件
import React, { PureComponent } from 'react'; import { Transition } from 'react-transition-group'; export class App extends PureComponent { constructor() { super(); this.nodeRef = React.createRef(null); this.state = { isShowTitle: true, defaultStyle: { transition: `opacity 1000ms ease-in-out`, opacity: 0, }, transitionStyles: { entering: { opacity: 1 }, entered: { opacity: 1 }, exiting: { opacity: 0 }, exited: { opacity: 0 }, }, }; } changeSonShow() { this.setState({ isShowTitle: !this.state.isShowTitle, }); } render() { const { isShowTitle, defaultStyle, transitionStyles } = this.state; return ( <div> <button onClick={() => this.changeSonShow()}> {isShowTitle ? '显示标题' : '隐藏标题'} </button> <Transition nodeRef={this.nodeRef} in={isShowTitle} timeout={1000}> {(state) => ( <div style={{ ...defaultStyle, ...transitionStyles[state], }} > 标题标题标题标题标题标题标题标题 </div> )} </Transition> </div> ); } } export default App;
【2】CSSTransition 组件
- CSSTransition是基于Transition组件构建的:
- 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:动画class的名称
- 决定了在编写css时,对应的class名称:比如card-enter、card-enter-active、card-enter-done;
-
timeout:
- 过渡动画的时间
-
appear:
- 是否在初次进入添加动画(需要和in同时为true)
-
unmountOnExit:
- 如果为true时,退出后将卸载组件
-
- CSSTransition对应的钩子函数:主要为了检测动画的执行过程,来完成一些JavaScript的操作
- onEnter:在进入动画之前被触发;
- onEntering:在应用进入动画时被触发;
- onEntered:在应用进入动画结束后被触发;
-
创建一个干净的脚手架
-
安装 react-transition-group
npm install react-transition-group --save
-
修改 src/App.js(App.jsx) 文件,引入CSSTransition 组件
import React, { PureComponent } from 'react'; import { CSSTransition } from 'react-transition-group'; import './style.css'; export default class App extends PureComponent { constructor() { super(); this.nodeRef = React.createRef(null); this.state = { isShow: true, }; } render() { const { isShow } = this.state; return ( <div> <button onClick={() => { this.setState({ isShow: !isShow }); }} > 显示/隐藏 </button> <CSSTransition nodeRef={this.nodeRef} in={isShow} timeout={1000} classNames="test" unmountOnExit onEnter={(e) => console.log('开始进入动画')} onEntering={(e) => console.log('进入动画进行中')} onEntered={(e) => console.log('进入动画结束')} onExit={(e) => console.log('开始退出动画')} onExiting={(e) => console.log('退出动画进行中')} onExited={(e) => console.log('退出动画结束')} > <h2 ref={this.nodeRef}>文字文字文字文字文字文字</h2> </CSSTransition> </div> ); } }
-
在src/App.js 文件所处的文件夹下创建 style.css 文件
.test-enter { opacity: 0; } .test-enter-active { opacity: 1; transition: opacity 1s ease; } .test-exit { opacity: 1; } .test-exit-active { opacity: 0; transition: opacity 1s, transform 1s; }
【3】SwitchTransition 组件
-
SwitchTransition可以完成两个组件之间切换的炫酷动画:
- 比如我们有一个按钮需要在on和off之间切换,我们希望看到on先从左侧退出,off再从右侧进入;
- 这个动画在vue中被称之为 vue transition modes;
- react-transition-group中使用SwitchTransition来实现该动画;
-
SwitchTransition中主要有一个属性:mode,有两个值
- in-out:表示新组件先进入,旧组件再移除;
- out-in:表示旧组件先移除,新组建再进入;
-
如何使用SwitchTransition呢?
- SwitchTransition组件里面要有CSSTransition或者Transition组件,不能直接包裹你想要切换的组件;
- SwitchTransition里面的CSSTransition或Transition组件不再像以前那样接受in属性来判断元素是何种状态,取而代之的是
key属性;
-
创建一个干净的脚手架
-
安装 react-transition-group
npm install react-transition-group --save
-
修改 src/App.js(App.jsx) 文件,引入SwitchTransition 和 CSSTransition 组件
import React, { PureComponent } from 'react'; import { SwitchTransition, CSSTransition } from 'react-transition-group'; import './style.css'; export class App extends PureComponent { constructor() { super(); this.nodeRef = React.createRef(null); this.state = { isLogin: false, }; } render() { const { isLogin } = this.state; return ( <div style={{ textAlign: 'center' }}> <SwitchTransition mode="out-in"> <CSSTransition nodeRef={this.nodeRef} key={isLogin ? 'exit' : 'login'} classNames="login" timeout={1000} > <button ref={this.nodeRef} onClick={(e) => this.setState({ isLogin: !isLogin, }) } > {isLogin ? '退出' : '登录'} </button> </CSSTransition> </SwitchTransition> </div> ); } } export default App;
-
在src/App.js 文件所处的文件夹下创建 style.css 文件
.login-enter { transform: translateX(100px); opacity: 0; } .login-enter-active { transform: translateX(0); opacity: 1; transition: all 1s ease; } .login-exit { transform: translateX(0); opacity: 1; } .login-exit-active { transform: translateX(-100px); opacity: 0; transition: all 1s ease; }
【4】TransitionGroup 组件
- 当我们有一组动画时,需要将这些CSSTransition放入到一个TransitionGroup中来完成动画:
-
创建一个干净的脚手架
-
安装 react-transition-group
npm install react-transition-group --save
-
修改 src/App.js(App.jsx) 文件,引入TransitionGroup 和 CSSTransition 组件
import React, { PureComponent } from 'react'; import { TransitionGroup, CSSTransition } from 'react-transition-group'; import './style.css'; export class App extends PureComponent { constructor() { super(); this.state = { curId: 4, books: [ { id: 1, name: '你不知道的JS', price: 99, nodeRef: React.createRef(null), }, { id: 2, name: 'JS高级程序设计', price: 88, nodeRef: React.createRef(null), }, { id: 3, name: 'Vue高级设计', price: 66, nodeRef: React.createRef(null), }, ], }; } addNewBook() { const { books, curId } = this.state; this.setState({ books: [ ...books, { id: curId, name: 'ES' + curId, price: 55, nodeRef: React.createRef(null) }, ], }); this.setState({ curId: curId + 1 }); } delBookHandle(index) { const { books } = this.state; this.setState({ books: books.filter((item, i) => i !== index), }); } render() { const { books } = this.state; return ( <div> <TransitionGroup component="ul"> {books.map((item, index) => { return ( <CSSTransition nodeRef={item.nodeRef} key={item.id} classNames="book" timeout={1000} > <li ref={item.nodeRef}> <span> {item.name}-{item.price} </span> <button onClick={() => this.delBookHandle(index)}> 删除 </button> </li> </CSSTransition> ); })} </TransitionGroup> <button onClick={() => this.addNewBook()}>添加书籍</button> </div> ); } } export default App;
-
在src/App.js 文件所处的文件夹下创建 style.css 文件
.book-enter { transform: translateX(100px); opacity: 0; } .book-enter-active { transform: translateX(0); opacity: 1; transition: all 1s ease; } .book-exit { transform: translateX(0); opacity: 1; } .book-exit-active { transform: translateX(150px); opacity: 0; transition: all 1s ease; }