第八节: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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
posted @ 2023-05-12 19:26  Yaopengfei  阅读(1118)  评论(0编辑  收藏  举报