虽然使用React构建应用程序在许多方面已经达到了一定程度的标准化,但是样式仍然是一个有许多解决方案的领域。 每种方案都有其优点和缺点,并没有明确的最佳选择。
在这篇文章中,我会简要介绍关于React组件在Web应用程序是是如何组织样式的。当然,我也会介绍下styled-components。
Javascript样式的进化之旅
CSS的初始版本发布在1996年,自那以来并没有多少变化。 在第三个主要版本中,以及第四个发行版,随着新特性的增加,CSS保持着它作为一项基础的互联网技术的地位。 CSS将永远是Web组件样式的黄金标准,虽然它的使用方式每天都在变化。
这些年我们搭建网站从切图到通过自定义CSS达到跟图片一样的效果,CSS随着Javascript和互联网同为一个平台的发展而进化。
随着2013年React的发布, 组件化的web应用成为标准。CSS的发展,反而,变的难以预测。主要反对在React中使用行内CSS是功能分离原则(SoC)。SoC是一个将代码划分为几块,每一块都关注于不同内容的设计原则。开发人员将这个原则应用于将三种主要web技术放在不同的文件夹里:样式(CSS),标签(HTML)和逻辑(Javascript)。
译者注:关于SoC(the separation of concerns),对比了维基百科和stackoverflow中的回答,觉得这篇更通俗易懂,感兴趣的可以点击进去看看,到底concerns是什么,简单解释就是Concerns是软件中不同功能的部分,比如业务逻辑是一个concerns,用户操作的界面又是一个concerns,它们之间应该保持独立,用户界面的修改不会影响业务逻辑模块,反之亦然。根据本文上下文,在此将concerns翻译成功能。
React引入的JSX改变了这个原则。React开发团队认为,我们一直在做的,实际上是,技术上的分离,而不是功能。有人可能会问,既然JSX把标签放到Javascript代码中了,为什么样式应该分离呢?
有很多方法可以把它们合并成行内来抵制样式和逻辑的分离。下面的例子就是:
<button style="background: red; border-radius: 8px; color: white;">Click Me</button>
行内样式转移了CSS文件内的样式定义。因此省略了引入CSS文件和节省了带宽,但是牺牲了可读性,可维护性以及样式继承。
CSS模块
button.css
.button {
background: red;
border-radius: 8px;
color: white;
}
button.js
import styles from './button.css';
document.body.innerHTML = `<button class="${styles.button}">test</button>`;
我们可以看到上面例子的代码,CSS仍然存在于它自己的文件里。然而,当CSS 模块通过Webpack或者其他打包工具打包,CSS作为一个script标签加载到HTML文件中。class名也映射为一个更细化的样式,解决了层叠样式表带来的问题。
映射的过程包括生成一个唯一的字符串来替代类名。btn类名会映射为一个DhtEg的哈希,可以阻止样式层叠以及样式应用到不需要的元素上。
index.html
<style>
.DhtEg {
background: red;
border-radius: 8px;
color: white;
}
</style>
…
<button class="DhtEg">test</button>
从上述例子我们可以看到由CSS模块添加的样式标签元素,和我们使用hash生成对应的类名和DOM元素。
Glamor
Glamor是一个CSS-in-JS的库,让我们可以在同一个文件里像定义我们的Javascript一样来定义CSS。Glamor,同样的,生成对应的哈希类名,但是提供了更简洁的语法来通过javascript来创建CSS样式表。
样式的定义是通过一个javascript变量来创建,这个变量使用驼峰语法来描述每个属性。驼峰命名的使用跟CSS在入门教程里定义所有属性一样重要。最大的区别是属性名的变化。从应用程序的其他部分或CSS示例复制和粘贴CSS时,这可能是一个问题。例如overflow-y将被更改为overFlowY。然而,通过这种语法更改,Glamam支持媒体查询和影子元素(shadow
elements),给我们的样式提供更多的能力:
button.js
import { css } from 'glamor';
const rules = css({
background: red;
borderRadius: 8px;
color: 'white';
});
const button = () => {
return <button {...rules}>Click Me</button>;
};
styled-components
styled-components是一个新的库,专注于保持React组件和样式结合。它给React和React Native提供了简洁易用的接口,styled-components不止改变了实现的方法还改变了构建React组件样式的思路。
styled-components可以通过npm安装:
npm install styled-components
像其他标准的npm包一样引用:
import styled from 'styled-components';
一旦安装完,就是时候开始让React组件的样式更加简单和享受。
构建React样式组件类
React样式组件可以从很多方面构建。styled-components库提供了让我们创建完美结构的UI应用模式。从小的UI组件构建-类似按钮,输入框,布局和选项卡-创建一个更加统一一致的应用。
使用上面例子的按钮组件,我们可以使用styled-components构建一个按钮类:
const Button = styled.button`
background: red;
border-radius: 8px;
color: white;
`;
class Application extends React.Component {
render() {
return (
<Button>Click Me</Button>
)
}
}
正如我们所看到的,我们通过使用javascript写行内CSS来创建按钮类。styled-components提供了一系列的可用元素。我们可以通过指向元素引用或者传递字符串给默认函数来实现。
const Button = styled.button`
background: red;
border-radius: 8px;
color: white;
`;
const Paragraph = styled.p`
background: green;
`;
const inputBg = 'yellow';
const Input = styled.input`
background: ${inputBg};
color: black;
`;
const Header = styled('h1')`
background: #65a9d7;
font-size: 26px;
`
class Application extends React.Component {
render() {
return (
<div>
<Button>Click Me</Button>
<Paragraph>Read ME</Paragraph>
<Input
placeholder="Type in me"
/>
<Header>I'm a H1</Header>
</div>
)
}
}
这种样式写法的主要优势是可以写原生CSS。就像Glamor例子中看到的,CSS属性名必须转变成驼峰命名,类似一个javascript对象的属性。 styled-components还对react很友好,就跟现在的react组件一样。使用Javascript模板字面量让我们可以应用CSS所有的特性到样式组件中。像上面输入框元素的例子,我们可以定义外部Javascript变量,然后应用到我们的样式中。 通过这些简单的组件,我们可以更简单的给应用构建样式规范。但是更多时候,我们还需要可以基于外部因素而改变的复杂组件。
--广告--
定制化React样式组件
styled-components的可定制化才是真正的核心。这通常可以应用于需要根据上下文更改样式的按钮。在这种情况下,我们有两个按钮规格 - 小号和大号。 以下是纯CSS方法:
CSS
button {
background: red;
border-radius: 8px;
color: white;
}
.small {
height: 40px;
width: 80px;
}
.medium {
height: 50px;
width: 100px;
}
.large {
height: 60px;
width: 120px;
}
JavaScript
class Application extends React.Component {
render() {
return (
<div>
<button className="small">Click Me</button>
<button className="large">Click Me</button>
</div>
)
}
}
当我们使用样式组件重构它时,我们创建一个有默认背景样式的按钮组件。 由于组件就像React组件一样,我们可以利用props并相应地改变样式效果:
const Button = styled.button`
background: red;
border-radius: 8px;
color: white;
height: ${props => props.small ? 40 : 60}px;
width: ${props => props.small ? 60 : 120}px;
`;
class Application extends React.Component {
render() {
return (
<div>
<Button small>Click Me</Button>
<Button large>Click Me</Button>
</div>
)
}
}
高级用法
样式组件提供了创建复杂高级组件的能力,我们可以使用现有的JavaScript模式来组合组件。 下面的示例演示了组件的组成方式 - 在一个通用基本样式的通知消息组件里,但每种类型都具有不同的背景颜色。 我们可以构建一个基本的样式组件作为父组件来创建高级组件:
const BasicNotification = styled.p`
background: lightblue;
padding: 5px;
margin: 5px;
color: black;
`;
const SuccessNotification = styled(BasicNotification)`
background: lightgreen;
`;
const ErrorNotification = styled(BasicNotification)`
background: lightcoral;
font-weight: bold;
`;
class Application extends React.Component {
render() {
return (
<div>
<BasicNotification>Basic Message</BasicNotification>
<SuccessNotification>Success Message</SuccessNotification>
<ErrorNotification>Error Message</ErrorNotification>
</div>
)
}
}
由于样式组件允许我们传递标准的DOM元素和其他组件,我们可以从基本组件中构建高级功能。
组件结构
从我们高级和基础示例中,我们可以构建一个组件结构。大多数标准的React应用程序都有一个组件目录:我们将样式组件放在styledComponents目录下。 我们的styledComponents目录包含所有组成的基本组件。 然后将这些导入到我们应用中使用的展示组件中。目录布局如下所示:
src/
components/
addUser.js
styledComponents/
basicNotification.js
successNotification.js
errorNotification.js
最后
正如我们在这篇文章中看到的那样,我们可以对组件样式实现的方式差别很大 - 没有一个是明确的解决方案。 这篇文章显示,styled-components推动了样式元素的实现,并根据现有方法引导我们质疑自己的思考方式。
每个包括我在内的开发人员都有自己喜欢的做事方式,根据我们正在使用的应用程序,了解不同的实现方法很好。 样式的系统和语言这些年来取得了长足的进步,毫无疑问,它们将来会进一步发展,更多发展。 这在前端开发中是非常令人激动和有趣的时刻。
你更喜欢哪种方式来给React组件写样式,并且为什么?