React中的样式

  • 目前整个前端已经是组件化的天下:
  • 而CSS的设计就不是为组件化而生的,所以在目前组件化的框架中都在需要一种合适的CSS解决方案。
  • 在组件化中选择合适的CSS解决方案应该符合以下条件:
  • 可以编写局部css:css具备自己的局部作用域,不会随意污染其他组件内的元素;
  • 可以编写动态的css:可以获取当前组件的一些状态,根据状态的变化生成不同的css样式;
  • 支持所有的css特性:伪类、动画、媒体查询等;
  • 编写起来简洁方便、最好符合一贯的css风格特点;
  • 等等...
  • 事实上,css一直是React的痛点,也是被很多开发者吐槽、诟病的一个点。
  • 在这一点上,Vue做的要确实要好于React:
  • Vue通过在.vue文件中编写 <style><style> 标签来编写自己的样式;
  • 通过是否添加 scoped 属性来决定编写的样式是全局有效还是局部有效;
  • 通过 lang 属性来设置你喜欢的 less、sass等预处理器;
  • 通过内联样式风格的方式来根据最新状态设置和改变css;
  • 等等...
  • Vue在CSS上虽然不能称之为完美,但是已经足够简洁、自然、方便了,至少统一的样式风格不会出现多个开发人员、多个项目
    采用不一样的样式风格。
  • 相比而言,React官方并没有给出在React中统一的样式风格:
  • 由此,从普通的css,到css modules,再到css in js,有几十种不同的解决方案,上百个不同的库;
  • 大家一致在寻找最好的或者说最适合自己的CSS方案,但是到目前为止也没有统一的方案;
内联样式
  • 内联样式是官方推荐的一种css样式的写法:
  • style 接受一个采用小驼峰(原生中使用的是连接符)命名属性的 JavaScript 对象<h2 style={{fontSize: "50px", color: "red"}}>我是标题</h2>,而不是 CSS 字符串;
  • 并且可以引用state中的状态来设置相关的样式;
  • style 中属性值需要用双引号或单引号引起来, 不然会被认为是变量
  constructor(props) {
    super(props);
    this.state = {
      color: "purple"
    }
  }
  render() {
    const pStyle = {
      color: this.state.color,
      textDecoration: "underline"
    }
    return (
      <div>
        <h2 style={{fontSize: "50px", color: "red"}}>我是标题</h2>
        <p style={pStyle}>我是一段文字描述</p>
      </div>
    )
  }
  • 内联样式的优点:
  • 1.内联样式, 样式之间不会有冲突
  • 2.可以动态获取当前state中的状态
  • 内联样式的缺点:
  • 1.写法上都需要使用驼峰标识
  • 2.某些样式没有提示,比如在标签内写 style 属性时有提示,但是如果像上面 pStyle 拿到外面写就不会有提示了
  • 3.大量的样式, 代码混乱
  • 4.某些样式无法编写(比如伪类/伪元素)
  • 所以官方依然是希望内联合适和普通的css来结合编写;
普通的 CSS
  • 普通的css我们通常会编写到一个单独的文件,之后再进行引入。
  • 这样的编写方式和普通的网页开发中编写方式是一致的:
  • 如果我们按照普通的网页标准去编写,那么也不会有太大的问题;
  • 但是组件化开发中我们总是希望组件是一个独立的模块,即便是样式也只是在自己内部生效,不会相互影响;
  • 但是普通的css都属于全局的css,样式之间会相互影响;
  • 这种编写方式最大的问题是样式之间会相互层叠掉;
    比如我们在 home 组件中有个 css 文件,写了个title 选择器的样式
.title {
  font-size: 30px;
  color: red;
}

然后在 profile 组件中有个 css 文件,也写了个 title 选择器的样式

.title {
  color: yellow;
}

由于 css 是全局的, 所以这两个 title 选择器就会发生层叠,也就是在使用到 title 选择器的地方要么都显示红色,要么都显示黄色, 不会是我们希望的 home 中显示红色,profile 中显示黄色

css modules
  • css modules并不是React特有的解决方案,而是所有使用了类似于webpack配置的环境下都可以使用的。
  • 但是,如果在其他项目中使用个,那么我们需要自己来进行配置,比如配置webpack.config.js中的modules: true等。
  • React的脚手架已经内置了css modules的配置:
  • .css/.less/.scss 等样式文件都修改成.module.css/.module.less/.module.scss 等;
  • 之后就可以引用并且进行使用了;
    比如我们定义了一个 style.module.css 文件,文件内容如下:
.title {
  font-size: 30px;
  color: red;
}
.banner {
  color: orange;
}

在 index.js 文件中的使用如下:

import React, { PureComponent } from 'react';
import homeStyle from './style.module.css'; // 这里我们按包导入的方式导入,在使用的时候都是用homeStyle.xxx 的方式使用

export default class Home extends PureComponent {
  render() {
    return (
      <div className="home">
        <h2 className={homeStyle.title}>标题</h2>
        <div className={homeStyle.banner}>
          <span>轮播图</span>
        </div>
      </div>
    )
  }
}
  • css modules确实解决了局部作用域的问题,也是很多人喜欢在React中使用的一种方案。
  • 但是这种方案也有自己的缺陷:
  • 引用的类名,不能使用连接符(.home-title),在JavaScript中是不识别的;
  • 所有的className都必须使用{style.className} 的形式来编写;
  • 不方便动态来修改某些样式,依然需要使用内联样式的方式;
  • 如果你觉得上面的缺陷还算OK,那么你在开发中完全可以选择使用css modules来编写,并且也是在React中很受欢迎的一种方式。
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的库依然非常强大、方便:
  • CSS-in-JS通过JavaScript来为CSS赋予一些能力,包括类似于CSS预处理器一样的样式嵌套、函数定义、逻辑复用、动态修
    改状态等等;
  • 虽然CSS预处理器也具备某些能力,但是获取动态状态依然是一个不好处理的点;
  • 所以,目前可以说CSS-in-JS是React编写CSS最为受欢迎的一种解决方案;
  • 目前比较流行的 CSS-in-JS 的库有 styled-componentsemotion , 前者是社区最流行的 CSS-in-JS库
styled-components
  • 安装命令: yarn add styled-components
  • 要学习styled-components首先要了解 ES6 的标签模板字符串,详见JavaScript中的 ES6标签模板字符串;
  • 在styled component中,就是通过这种方式来解析模块字符串,最终生成我们想要的样式的
  • styled-components的本质是通过函数的调用,最终创建出一个组件:
  • 这个组件会被自动添加上一个不重复的class;
import React, { Component } from 'react';
import styled from 'styled-components'
const DivCmp = styled.div`
  color:red;
`
export default class App extends Component {
  render() {
    return (
      <DivCmp>
        <h2>用户昵称</h2>
      </DivCmp>
    )
  }
}

如上图 styled.div`...` 会给我返回一个 div 类型的标签,styeld 后面跟什么就会返回什么类型,如果是 h1, 返回的就是h1,如果是 span,就返回 span, 如果没有这种类型会不显示,

  • styled-components会给该class添加相关的样式;如上图生成一个给 div 添加 class="sc-hKMtZM gt0Tnf" 的样式,这个字符串是唯一的,不会与别的重复
  • props 属性穿透, props 可以被传递给 styled 组件,获取 props 需要通过${}传入一个插值函数,props 会作为该函数的参数,这种方式可以有效解决动态样式的问题
import React, { Component } from 'react';
import styled from 'styled-components'
const DivCmp = styled.div`
  color:red;
  font-size: ${props=>props.fontSize};
  text-decoration: ${props=>props.textDecoration};
`
export default class App extends Component {
  render() {
    return (
      <DivCmp fontSize="55px" textDecoration="underline">
        <span>用户昵称</span>
      </DivCmp>
    )
  }
}
  • attrs 函数, 通过styled-components创建出来的 styled 组件都会有一个attrs函数, 该函数接收一个对象作为参数, 并返回 styled 组件。
  • 参数中我们既可以放置组件的属性,也能放置组件的样式
import React, { Component } from 'react';
import styled from 'styled-components'

const HYInput = styled.input.attrs({
  placeholder: "请输入文字", // 这里和HYInput组件中设置placeholder属性是一样的, 
  bColor: "red" // 这里和在HYInput组件中设置一个 bColor 属性是一样的,可以通过 props 来取
})`
  background-color: lightblue;
  border-color: ${props => props.bColor}; // 取出 attrs 中的 bColor
  color: ${props => props.color};// 取出通过属性传过来的 color
`
export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      color: "purple"
    }
  }
  render() {
    return (
      <div>
        {/* 从 state 中取出 color 赋值给 color 属性, 这样就能动态改变样式了 */}
        <HYInput type="password" color={this.state.color}/>
      </div>
    )
  }
}
  • 继承, styled组件支持继承, 当很多组件有同样的样式时,我们就可以将同样的样式抽取成一个基类,然后不一样的样式部分再实现子类,通过子类继承基类来达到样式的服用。下面是继承的例子子组件通过styled(父组件名)的方式来继承
import React, { PureComponent } from 'react';
import styled from 'styled-components';

const HYButton = styled.button`
  padding: 10px 20px;
  border-color: red;
  color: red;
`

const HYPrimaryButton = styled(HYButton)`
  color: #fff;
  background-color: green;
`

export default class App extends PureComponent {
  render() {
    return (
        <HYButton>我是普通的按钮</HYButton>
        <HYPrimaryButton>我是主要的按钮</HYPrimaryButton>
    )
  }
}
  • styled 提供了 ThemeProvider 来共享主题
import React, { PureComponent } from 'react';
import styled, { ThemeProvider } from 'styled-components';

const TitleWrapper = styled.h2`
  text-decoration: underline;
  color: ${props => props.theme.themeColor};
  font-size: ${props => props.theme.fontSize};
  `

class Home extends PureComponent {
  render() {
    return (
      <TitleWrapper>我是home的标题</TitleWrapper>
    )
  }
}

export default class App extends PureComponent {
  render() {
    return (
      <ThemeProvider theme={{themeColor: "red", fontSize: "25px"}}>
        <Home />
      </ThemeProvider>
    )
  }
}
React 中动态修改class
  • React在JSX给了我们开发者足够多的灵活性,你可以像编写JavaScript代码一样,通过一些逻辑来决定是否添加某些class:
<div>
  <h2 className={"title " + (isActive ? "active" : "")}> 我是标题</h2>
  <h2 className={["title ", (isActive ? "active" : "")].join(" ")}> 我是标题</h2>
</div>
  • 我们也可以借助第三方库: classnames, 这是一个用于动态添加 className 的库
classNames('foo','bar'); // 'foo bar'
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('foo', {bar: true, duck: false}, 'baz', {quux: true}); // 'foo bar baz quux'
classNames(null,false,'bar',undefined, 0,1,{baz: null},''); // 'bar 1'
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容