第八节:react编写css的六种方案剖析
一. 前言
1. 背景
事实上,css一直是React的痛点,也是被很多开发者吐槽、诟病的一个点。
2. 对比vue
(1) 在这一点上,Vue做的要好于React:
Vue通过在.vue文件中编写 <style><style> 标签来编写自己的样式; 通过是否添加 scoped 属性来决定编写的样式是全局有效还是局部有效; 通过 lang 属性来设置你喜欢的 less、sass等预处理器; 通过内联样式风格的方式来根据最新状态设置和改变css;
等等...
(2) Vue在CSS上虽然不能称之为完美,但是已经足够简洁、自然、方便了,至少统一的样式风格不会出现多个开发人员、多个项目采用不一样的样式风格。
3. React官方并没有给出在React中统一的样式风格:
由此,从普通的css,到css modules,再到css in js,有几十种不同的解决方案,上百个不同的库;
大家一致在寻找最好的或者说最适合自己的CSS方案,但是到目前为止也没有统一的方案;
二. 内联样式
1. 内联样式是官方推荐的一种css样式的写法
style 接受一个采用小驼峰命名属性的 JavaScript 对象,,而不是 CSS 字符串;并且可以引用state中的状态来设置相关的样式;
即: font-size 需要写成: fontSize, 因为js中不支持 xxx-xxx的变量声明
export class App extends PureComponent {
constructor() {
super();
this.state = {
mySize: 30,
};
}
addSize() {
this.setState({
mySize: this.state.mySize + 1,
});
}
render() {
const { mySize } = this.state;
return (
<div>
<div style={{ color: "red", fontSize: `${mySize}px` }}>我是标题</div>
<p style={{ color: "blue", fontSize: "20px", fontWeight: "bold" }}>
我是内容
</p>
<button onClick={() => this.addSize()}>增加标题的size</button>
</div>
);
}
}
2. 内联样式的优点
(1). 内联样式, 样式之间不会有冲突
(2). 可以动态获取当前state中的状态
3. 内联样式的缺点
(1). 写法上都需要使用驼峰标识
(2). 某些样式没有提示
(3). 大量的样式, 代码混乱
(4). 某些样式无法编写(比如伪类/伪元素)
三. 普通的CSS写法
1. 说明 【不推荐】
普通的css我们通常会编写到一个单独的文件,之后再进行引入。 这样的编写方式和普通的网页开发中编写方式是一致的: 如果我们按照普通的网页标准去编写,那么也不会有太大的问题;
但是组件化开发中我们总是希望组件是一个独立的模块,即便是样式也只是在自己内部生效,不会相互影响;
但是普通的css都属于全局的css,样式之间会相互影响!!!!
2. 缺点
这种编写方式最大的问题是样式之间会相互层叠掉!!!
3. 实操
App中引入Home组件、Profile组件,其中在Home组件中编写了 myTitle样式,该样式作用全局,Profile和App组件都生效了。
四. CSS-Module写法
1. 说明
(1).css modules并不是React特有的解决方案,而是所有使用了类似于webpack配置的环境下都可以使用的。
如果在其他项目中使用它,那么我们需要自己来进行配置,比如配置webpack.config.js中的modules: true等。
(2).React的脚手架已经内置了css modules的配置:
.css/.less/.scss 等样式文件都需要修改成 .module.css/.module.less/.module.scss 等; 之后就可以引用并且进行使用了;
(3). css modules确实解决了局部作用域的问题,也是很多人喜欢在React中使用的一种方案。
2. 局限
(1).引用的类名,不能使用连接符(.home-title),在JavaScript中是不识别的;
(2).所有的className都必须使用{style.className} 的形式来编写;
(3).不方便动态来修改某些样式,依然需要使用内联样式的方式;
3. 实操
(1). App中引入Home组件、Profile组件,其中在Home组件中编写了 myTitle样式
(2). Home组件对应的样式文件改为 Home.module.css
(3). esmodule的形式默认导入 import homeStyle from "./Home.module.css"
(4). 使用的时候 需要 className={homeStyle.myTitle}
(5). 最终结果,该样式仅作用域Home组件
五. Less写法
(参考:https://www.npmjs.com/package/craco-less?activeTab=readme )
1. 准备
(1). 安装程序集:【npm install @craco/craco】【npm install craco-less@2.1.0-alpha.0】
PS: 2.0.0 版本不兼容最新的react
(2). 编写配置文件 craco.config.js
const CracoLessPlugin = require("craco-less");
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: { "@primary-color": "#1DA57A" },
javascriptEnabled: true,
},
},
},
},
],
};
(3). 在package.json文件中scripts中添加
"scripts": {
"serve": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"start1": "craco start",
"build1": "craco build",
"test1": "craco test"
},
2. 实操
运行 【npm run start1】进行运行,less代码生效
运行 【npm run build1】进行打包,打包后的less代码生效
六. css-in-js写法
1. 说明
(1).官方文档也有提到过CSS in JS这种方案:
“CSS-in-JS” 是指一种模式,其中 CSS 由 JavaScript 生成而不是在外部文件中定义; 注意此功能并不是 React 的一部分,而是由第三方库提供; React 对样式如何定义并没有明确态度;
(2).在传统的前端开发中,我们通常会将结构(HTML)、样式(CSS)、逻辑(JavaScript)进行分离。
但是在前面的学习中,我们就提到过,React的思想中认为逻辑本身和UI是无法分离的,所以才会有了JSX的语法。
样式呢?样式也是属于UI的一部分;
事实上CSS-in-JS的模式就是一种将样式(CSS)也写入到JavaScript中的方式,并且可以方便的使用JavaScript的状态;
所以React有被人称之为 All in JS;
(3).当然,这种开发的方式也受到了很多的批评:
Stop using CSS in JavaScript for web development
https://hackernoon.com/stop-using-css-in-javascript-for-web-development-fa32fb873dcc
(4).目前比较流行的CSS-in-JS的库有哪些呢?
styled-components
emotion
glamorous
2. styled-components
目前可以说styled-components依然是社区最流行的CSS-in-JS库
VsCode: 需要安装插件 【vscode-styled-components】,便于代码提示
包括:styled.xxx`` 编写样式、 props传递属性、attrs添加属性、继承、设置主题等功能
3. 实操--非常繁琐
(1). 安装 【npm install styled-components】
(2). 测试:基本用法、props接收外来参数、单独文件引入变量、attrs提供属性、继承等功能
AppStyle.js
查看代码
import styled from "styled-components";
import { primaryColor, largeSize } from "./variables/index";
//1 基本用法
export const AppWrapper = styled.div`
.footer {
border: 1px solid orange;
}
`;
// 2 通过props接收外来参数
// 3 从单独文件中引入变量
export const SectionWarpper1 = styled.div`
border: 1px solid red;
.title {
font-size: ${props => props.size}px;
color: ${props => props.color};
/* & 表示它所在的选择器,这里指 .title */
&:hover {
background-color: purple;
}
}
.content {
font-size: ${largeSize}px;
color: ${primaryColor};
}
`;
// 4. 可以通过attrs给标签模板字符串中提供的属性
/*
myColor是自定义的属性名,如果传递的参数props.color为空,则赋默认值 "blue"
*/
export const SectionWarpper2 = styled.div.attrs(props => ({
myColor: props.color || "blue",
}))`
border: 1px solid red;
.title {
font-size: ${props => props.size}px;
color: ${props => props.myColor};
/* & 表示它所在的选择器,这里指 .title */
&:hover {
background-color: purple;
}
}
.content {
font-size: ${largeSize}px;
color: ${primaryColor};
}
`;
// 5. 继承的写法
const YpfButton = styled.button`
border: 1px solid red;
border-radius: 4px;
`;
export const YpfButtonWapper = styled(YpfButton)`
background-color: #0f0;
color: #fff;
`;
index.js
export const primaryColor = "#ff8822";
export const secondColor = "#ff7788";
export const smallSize = "12px";
export const middleSize = "14px";
export const largeSize = "18px";
App.jsx
查看代码
import {
AppWrapper,
SectionWarpper1,
SectionWarpper2,
YpfButtonWapper,
} from "./AppStyle";
export class App extends PureComponent {
constructor() {
super();
this.state = { size: 30, color: "green" };
}
render() {
const { size, color } = this.state;
return (
<AppWrapper>
{/* 1. 基本用法 */}
<div className="footer">
<p>免责声明</p>
<p>版权声明</p>
</div>
{/* 2. 属性传值 */}
<SectionWarpper1 size={size} color={color}>
<h2 className="title">我是标题</h2>
<p className="content">我是内容, 哈哈哈</p>
<button onClick={e => this.setState({ color: "skyblue" })}>
修改颜色
</button>
</SectionWarpper1>
{/* 3. 测试attrs的使用,只传递size,不传递color */}
<SectionWarpper2 size={size}>
<h2 className="title">我是标题</h2>
<p className="content">我是内容, 哈哈哈</p>
<button onClick={e => this.setState({ size: 35 })}>修改大小</button>
</SectionWarpper2>
{/* 4. 继承的写法 */}
<YpfButtonWapper>我是按钮</YpfButtonWapper>
</AppWrapper>
);
}
}
4. ES6标签模板字符串
详见:https://www.cnblogs.com/yaopengfei/p/16027402.html
七. classNames库用法 ---强烈推荐
1. 说明
(1).在vue中添加class非常简单,可以绑定一个对象、一个数组、对象数组混合使用
:class="{active:isActive,"test-danger":hasError}" ---绑定对象
:class="[activeClass,errorClass]" ---绑定数组
:class="[{active:isActive},errorClass]" ---绑定对象和数组
(2).在react的jsx中,可以通过js代码控制逻辑,但是写法繁琐
render() {
const { isFlag1, isFlag2 } = this.state;
const classList = ["aaa"];
if (isFlag1) classList.push("bbb");
if (isFlag2) classList.push("ccc");
const myClassNameStr = classList.join(" ");
return (
<div>
{/* 案例1:js自己写逻辑 */}
<div>
<p>案例1:js自己写逻辑</p>
{/* aa bb */}
<h3 className={`aa ${isFlag1 ? "bb" : ""} ${isFlag2 ? "cc" : ""}`}>
ypf0
</h3>
{/* aaa bbb */}
<h3 className={myClassNameStr}>ypf0</h3>
</div>
</div>
);
}
2. classNames库用法
(详见:https://www.npmjs.com/package/classnames)
支持传入字符串、数组、对象,以及他们的混合使用
classNames('foo', 'bar'); // => 'foo bar'
classNames(["aa", "bb", "cc"]) // => 'aa bb ccr'
classNames('foo', { bar: true }); // => 'foo bar'
classNames({ 'foo-bar': true }); // => 'foo-bar'
classNames({ 'foo-bar': false }); // => ''
classNames({ foo: true }, { bar: true }); // => 'foo bar'
classNames({ foo: true, bar: true }); // => 'foo bar'
classNames(["aa", { bb: isFlag1, cc: isFlag2 }]) // => 'aa bb'
classNames('foo', { bar: true, duck: false }, 'baz', { quux: true }); // => 'foo bar baz quux'
classNames(null, false, 'bar', undefined, 0, 1, { baz: null }, ''); // => 'bar 1'
3. 实操
(1). 安装库 【npm install classnames】
(2). 编写代码进行测试, 详见案例2
export class App extends PureComponent {
constructor() {
super();
this.state = { isFlag1: true, isFlag2: false };
}
render() {
const { isFlag1, isFlag2 } = this.state;
return (
<div>
{/* 案例2:classNames库的使用 */}
<div>
<p>案例2:classNames库的使用</p>
{/*字符串 aa bb cc */}
<h4 className={classNames("aa", "bb", "cc")}>ypf1</h4>
{/*数组 aa bb cc */}
<h4 className={classNames(["aa", "bb", "cc"])}>ypf1</h4>
{/* 字符串和对象混合使用 aa bb */}
<h4 className={classNames("aa", { bb: isFlag1, cc: isFlag2 })}>
ypf1
</h4>
{/* 数组和对象混合使用 aa bb */}
<h4 className={classNames(["aa", { bb: isFlag1, cc: isFlag2 }])}>
ypf1
</h4>
</div>
</div>
);
}
}
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。