React是一个构建用户界面的js库,从UI=render()这个等式中就很好的映射了这一点,UI的显示取决于等式右边的render函数的返回值.css
而编写React应用,就是在编写React组件,组件中最重要的数据就是props和state,有了数据,怎么让其以什么样的显示,那就是CSS作的事情了html
在React中,一切皆能够是Js,也就是说在js里面能够写css,这相比传统的内容(html),层叠样式(css),行为动做(js)进行分离,这种分离仅仅是把三个不一样的技术进行了物理上的分离,进行分开管理,若是从另外一个视觉角度上讲,并无实现高内聚的特色前端
既然前端自己就是页面的展现,那么把js和css放在一块儿,也是一种细粒度的组合,css也能够和Js同样,经过模块化的形式嵌入到js里面去node
CSS modules很好的解决了样式冲突,利用了分而治之的理念,在现在组件化开发大行其道上,一样css也在不断的进化,如同js同样,也有变量,函数等具有Js同样的活力,那么在React中是怎么实现样式的模块化的?react
经过单独的*.css文件定义组件的样式,而且经过clssName指定他们,有什么很差的?git
在JSX上进行事件的监听绑定,经过on*EventType只针对原生的HTML标签起做用的,若是是自定义的组件,是不起做用的,有什么好的解决办法?github
样式化组件的魅力(特色)npm
那么本节就是你想要知道的canvas
若是想阅读体验更好,戳该连接,React中编写样式css(styled-components)数组
React中组件形式
关于React中定义组件的形式,有以下几种方式,其中前两个在以前的学习当中,相信你已经很熟悉了的,若是不清楚,能够查看前面的内容的
类class声明的组件(类组件/容器组件)
函数式声明的组件(函数组件/无状态组件/UI组件)
样式化组件(styled-components)
本节主要讲的就是样式化组件,给一个React组件添加样式,有哪些方式?下面一块儿来看看的
行内样式 VS 外部样式
想要给React组件添加样式,常见的方式有
在JSX上添加style属性定义行内样式
经过import关键字引入外部样式
像以下所示,在JSX上添加样式: 下面的代码是用class类组件声明了一个Header组件,这个组件返回了一个button按钮,给这个按钮经过style添加了一些样式
import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
class Header extends Component {
render(){
return (
<Fragment>
<button style = {{ width: '100px', height: '40px', borderRadius: '3px', outline: 'none', outline: 'none', border: 'none', cursor: 'pointer', background: '#abcdef', color: '#fff'}}>button按钮</button>
</Fragment>
);
}
}
const container = document.getElementById('root');
ReactDOM.render(<Header />, container);
复制代码
对于上面的行内样式,也能够把它定义成一个对象Object的方式去定义样式,与下面是等价的
class Header extends Component {
render(){
const btnStyle = {
width: '100px',
height: '40px',
borderRadius: '3px',
outline: 'none',
border: 'none',
cursor: 'pointer',
background: '#abcdef',
color: '#fff'
}
return (
<Fragment>
<button style = { btnStyle }>button按钮</button>
</Fragment>
);
}
}
复制代码
虽然这样也是在JS中写css样式,可是管理起来并不方便,不少时候,咱们是用clssName的方式去定义样式的 ,按照咱们熟悉的方式,就是把样式文件命名成*.css,而后经过import的方式给引入进去
import "./style.css";
复制代码
对于样式名,有时候,对于各个不一样的组件的className有可能会同样,若是是这样的话,后面引入的样式名会覆盖前面的,这样的话显然不是咱们想要的结果了
那有什么好的解决办法?
在React中有css-in-js,它是一种模式,这个css由js生成而不是在外部文件中定义,是CSS Modules,主要是借助第三方库生成随机类名称的方式来创建一种局部类名的方式
这种css-in-js的第三方模块有不少:能够访问:github.com/MicheleBert…
今天的主要学习的是github上star数最多的,styled-components
使用styled-components的好处是:它可让组件本身的样式对本身生效,不是全局生效,作到互不干扰
首先你得经过npm或者cnpm进行安装styled-components模块
npm install -S styed-components
复制代码
在安装完后,在使用styled-components的文件内,经过import的方式引入该模块
以下代码所示: 在文件的上方引入styled-components,实例化了一个styled对象,经过给styled对象下添加你想要的html元素,利用了Es6中的一个模板字符串,反引号
import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
import styled from "styled-components"; // 引入styled-components库,实例化styled对象
// 声明样式ButtonA组件,经过styled对象进行建立,注意styled.html元素,后面是反引号
const ButtonA = styled.button`
width: 100px;
height: 40px;
border-radius: 3px;
outline: none;
border: none;
cursor: pointer;
background: #abcdef;
color: #fff;
`;
// 样式化声明ButtonB组件
const ButtonB = styled.button`
background: red;
color: #fff;
width: 100px;
height: 40px;
border-radius: 3px;
outline: none;
border: none;
cursor: pointer;
`;
class Header extends Component {
render(){
return (
<Fragment>
<ButtonA>按钮A</ButtonA>
<ButtonB>按钮B</ButtonB>
</Fragment>
);
}
}
const container = document.getElementById('root');
ReactDOM.render(<Header />, container);
复制代码
这是渲染的结果:
下面这种作法是不推荐的,应当避免使用
class Header extends Component {
render() {
const ButtonA = styled.button`
width: 100px;
height: 40px;
border-radius: 3px;
outline: none;
border: none;
cursor: pointer;
background: #abcdef;
color: #fff;
`;
// 声明样式ButtonB组件,不该该在render函数里面声明样式组件
const ButtonB = styled(ButtonA)`
background: red;
`;
return (
<Fragment>
<ButtonA>按钮A</ButtonA>
<ButtonB>按钮B</ButtonB>
</Fragment>
);
}
}
复制代码
由于在render方法中声明样式化组件,每次都会动态渲染建立一个新的组件。这意味着React必须在每一个后续渲染中丢弃并从新计算DOM树的那部分,而不是仅计算它们之间发生变化的差别。这会致使性能瓶颈
正确的作法就是如同刚开始那样,把样式组件放到组件最外面去
固然,为了便于样式的集中管理,对于样式化组件,咱们每每会把它写在一个文件中去,把上面的样式组件放到一个style.js的文件中去,而后经过Es6中模块化的export的方式对外暴露出去,只要哪一个组件模块须要,直接经过import引入便可
import styled from "styled-components"; // 引入styled-components
// 声明样式ButtonA组件
const ButtonA = styled.button`
width: 100px;
height: 40px;
border-radius: 3px;
outline: none;
border: none;
cursor: pointer;
background: #abcdef;
color: #fff;
`;
// 声明样式ButtonB组件
const ButtonB = styled.button`
background: red;
color: #fff;
width: 100px;
height: 40px;
border-radius: 3px;
outline: none;
border: none;
cursor: pointer;
`;
// 对外暴露出去
export {
ButtonA,
ButtonB
}
复制代码
细心的朋友你会发现,其实两个按钮有不少相同的样式,只有背景颜色不同而已,若是重复写不少样式,那么确定是有不少冗余的代码,styled-components中提供了继承的能力
要建立一个继承另外一个样式的新组件,只需将其包装在styled(继承的组件)构造函数中便可,以下所示
// 声明样式ButtonA组件
const ButtonA = styled.button`
width: 100px;
height: 40px;
border-radius: 3px;
outline: none;
border: none;
cursor: pointer;
background: #abcdef;
color: #fff;
`;
// 声明样式ButtonB组件,同时样式ButtonB组件继承了ButtonA组件的样式,又给自身拓展了样式,更改了自身的背景色
const ButtonB = styled(ButtonA)`
background: red;
`;
复制代码
在要使用样式组件的地方经过import引入ButtonA,ButtonB组件
import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
import {
ButtonA,
ButtonB
}
from './style'
class Header extends Component {
render(){
return (
<Fragment>
<ButtonA>按钮A</ButtonA>
<ButtonB>按钮B</ButtonB>
</Fragment>
);
}
}
const container = document.getElementById('root');
ReactDOM.render(<Header />, container);
复制代码
样式组件能够接收props
对于组件外部定义的属性,在样式组件内能够进行接收,写一些简单的逻辑表达式 以下所示:在肯定按钮组件内设置了一个color属性,在样式组件内能够经过props进行接收
import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
import {
Button
}
from './style'
class Header extends Component {
render() {
return (
<Fragment>
<Button>取消</Button>
<Button color="red">肯定</Button>
</Fragment>
);
}
}
const container = document.getElementById('root');
ReactDOM.render(<Header />, container);
复制代码
在样式组件内,属性值能够经过Es6中的插值表达式,${表达式}的方式进行指定的逻辑判断,达到本身想要的目的
import styled from "styled-components"; // 引入styled-components
// 声明样式Button组件
const Button = styled.button`
width: 100px;
height: 40px;
border-radius: 3px;
outline: none;
border: none;
cursor: pointer;
background: ${props => props.color ? "red": '#fff'};
color: ${(props) => props.color ? '#fff': 'red'};
border: 2px solid red;
margin-right: 15px;
`;
// 对外暴露出去
export {
Button
}
复制代码
渲染的结果以下所示
例如:本身封装一些本身组件,不一样大小按钮等等的,经过在组件外部设置属性值,而后在样式组件内部进行接收,控制组件的样式UI形态
固然这种简单的样式处理,彻底是能够用上面继承的方式去处理的
值得注意的是,在插入背景图片时,是不支持直接插入的,这样是不生效的
const Content = styled.div`
width: 550px;
height: 290px;
background:url('./react.jpg');
`;
复制代码
引入本地图片必须得经过定义变量的方式来实现,以下所示
import BgImg from './react.jpg'; // 将图片定义成一个变量的方式来引用
const Content = styled.div`
width: 550px;
height: 290px;
background: url(${BgImg}); // 注意这里用Es6中的模板语法
`;
复制代码
.attrs方法支持给组件添加属性
attrs是一个构造方法,能够给样式组件添加自身的额外属性(这个属性只容许html标签原生自有的属性),不支持自定义属性,要想添加自定义属性,只能在jsx元素上进行添加
attrs可接收两种类型的参数:
参数能够接收一个对象,经过它添加的属性,会被合并到样式组件当中去
参数能够是一个函数,若是有props值,则可以使用该模式 以下代码所示
import styled from "styled-components"; // 引入styled-components
// 参数是一个对象
const Input = styled.input.attrs({
placeholder: '请输入信息',
type: 'text'
})`
width:${props => props.width};
height: ${props => props.size === 'small'? '24px': '40px'};
font-size: 14px;
text-indent: 10px;
border-radius: 3px;
border: 1px solid palevioletred;
display: block;
margin: 0 0 1em;
::placeholder {
color: palevioletred;
}
`
// 对外暴露出去
export {
Input
}
复制代码
在要引用组件处
import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
import {
Input
}
from './style'
class Header extends Component {
render() {
return (
<Fragment>
<Input width="150px" placeholder="请输入帐号" size="small"/>
<Input width="200px" placeholder="请输入密码" size='large' type="password" />
</Fragment>
);
}
}
const container = document.getElementById('root');
ReactDOM.render(<Header />, container);
复制代码
渲染结果以下所示:
const Input = styled.input.attrs(props => ({ // 参数是一个函数,能够经过props进行接收
placeholder: '请输入信息',
type: 'text'
}))`
width:${props => props.width};
height: ${props => props.size === 'small'? '24px': '40px'};
// 以下省略
`
复制代码
注意:关于样式的优先级
行内样式>外部样式(样式组件),若是行内元素设置的了默认值,则行内样式优先
不然,在attrs内设置的属性会覆盖外部的属性
至于何时用attrs
使用attrs将属性传递给样式化组件
当你但愿样式化组件的每一个实例都具备该prop时使用attrs,换句话说,经过attrs设置的属性,它是公共的,若是每一个实例须要不一样的实例时则可直接传递props
如何覆盖默认样式
有时候,须要覆盖样式最粗鲁的方式就是在属性后面加权重,经过!important来完成,但这很容易出错,而且很容易出问题
具体的实现方式是经过&符号的方式,每添加一个&符号,都会随机生成一个类样式
const ButtonB = styled(ButtonA)`
&&& {
color: palevioletred;
font-weight: bold;
}
`
复制代码
以下图所示
如何覆盖内联样式
内联样式的优先级是最高的,始终优先于外部CSS,所以没法经过简单地样式组件覆盖它,可是有具体的解决办法的, 就是使用&[style]和!important加权重的方式
有时候,若是在JSX上声明了行内样式,可是外部想要覆盖它,那么这个时候,&[style]和import加权重的方式就颇有用了的,可是在实际开发中,应该避免使用行内样式,在这里只是为了说明诸如此类的解决办法
const ButtonB = styled(ButtonA)`
&[style]{
background: blue !important;
font-weight: bold;
}
`;
复制代码
一样,每追加一个&符号,都会新添加一个类,在实际的项目中,应当少用行内样式的,不要一时爽,后面给本身挖坑的
重置全局样式
对于React中重置默认样式,它使用的是createGlobalStyle这个函数,须要从styled-components中注入 以下所示:
import { createGlobalStyle } from 'styled-components'
const globalStyle = createGlobalStyle`
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}
`
export default globalStyle;
复制代码
通常而言,把重置样式单独的定义成一个文件,单独的引入到index.js当中去的,全局生效的
须要注意的是:在早先的版本中使用全局的方式是injectGlobal,而这个API已经废弃,并由styled-components v4中的createGlobalStyle替换了
CSS-module编写样式
在使用create-react-app脚手架建立的项目后,该项目是支持css-module的
可是须要注意如下几点:
样式文件的名称必须以xxx.module.css或者xxx.module.scss的形式命名:例如styles.module.css或者styles.module.scss
以变量的形式导入样式文件,好比:import styles from './style.module.css',若是是直接导入xxx.css,在JSX元素上的className的属性名称,是没法经过变量对象引入样式的,若是是直接引入样式,则在className的属性值中直接引入类名便可
className以变量引用的方式进行添加,例如:className ={styles.counter}
使用sass时,脚手架建立的项目,默认是支持sass的,使用时只须要安装一下node-sass这个包便可
在根文件夹下定义styles.module.css文件,写入以下几行样式代码
.counter{
text-align: center;
}
.button{
padding: 0 10px;
}
.spanText{
padding: 0 15px;
}
复制代码
在使用css-module样式的文件内,经过import的方式引入该xxx.module.css文件
import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
import styles from './styles.module.css'; // 引入styles.module.css,实例化一个styles对象
class Counter extends Component {
constructor(props){
super(props);
this.state = {
count: 0
}
}
increment = () => this.setState({ count: this.state.count + 1 })
decrement = () => this.setState({ count: this.state.count - 1 })
render() {
return (
<Fragment>
<div className={styles.counter}>
<button className={styles.button} onClick={this.decrement}>
-
</button>
<span className={styles.spanText}>{this.state.count}</span>
<button className={styles.button} onClick={this.increment}>
+
</button>
</div>
</Fragment>
)
}
}
const container = document.getElementById('root');
ReactDOM.render(<Counter />, container);
复制代码
具体效果以下所示:
import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
import styled from "styled-components"; // 引入styled-components
// 在Render函数外定义样式组件
const CounterWrap = styled.div`
text-align: center;
`
const SpanText = styled.span`
padding: 0 15px;
`
const Button = styled.button`
padding: 0 10px;
`
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
}
}
increment = () => this.setState({ count: this.state.count + 1 })
decrement = () => this.setState({ count: this.state.count - 1 })
render() {
return (
<Fragment>
<CounterWrap>
<Button onClick={this.decrement}>-</Button>
<SpanText>{this.state.count}</SpanText>
<Button onClick={this.increment}>+</Button>
</CounterWrap>
</Fragment>
)
}
}
const container = document.getElementById('root');
ReactDOM.render(<Counter />, container);
复制代码
固然你能够跟以前同样,把样式组件单独的抽离出去的,而后经过export对外暴露出去的,当须要使用时,在另外一文件内经过import引入便可
对于样式组件的命名:由于是组件,因此约定俗成的首字母大写,这是为了区分普通的html标签元素
小tip:在vs-code推荐插件:vscode-styled-components
下面来总结一些styled-components的一些特性
styled-components支持的特性
支持嵌套,变量和继承:可使用相似sass,less的语法嵌套,可使用变量来设置不一样的样式,使用这些不一样样式时只须要给样式组件传递一个参数就能够了的,在样式化组件内部能够经过props来接收外部的的参数值
事件监听绑定:对于自定义的样式化组件能够进行事件监听的绑定,这正是解决类class声明的自定义组件,没法绑定事件监听的痛点,onEventType事件类型只针对原生HTML标签才起做用,而样式化组件正好弥补了这一点
模块化css:按需引入组件的代码,避免了一些多余的代码
惟一类名,没有类名错误,重复:styled-components生成的样式生成惟一的类名。永远没必要担忧重复,重叠或拼写错误
更容易的删除样式,维护简单:编写的样式都与特定组件相关联,若是组件未使用(工具能够检测到)并被删除,则全部样式都将被删除,保持功能性的单一,达到了高内聚,低耦合的组件化特色
动态样式:样式组件内能够接收参数,很简单地调整和拓展组件的样式,而不须要创建不少个 class 类来维护组件的样式
本文主要讲解了React编写样式的姿式,并非什么高大上的内容,比较基础
经过styled-components第三方库的的引入,编写样式化组件,这个并非必须的,视项目公司团队而定,不使用这个,经过css-module编写React的样式也是能够的
固然如果使用了styled-components,便解决了一些问题,例如,样式覆盖,命名等痛点,以及解决了在类声明组件当中,没法给自定义组件绑定事件的问题
本文只是学习了styled-components的一些经常使用的知识,至于更多styled-components详细的使用:能够多查阅styled-components的官方文档的