React中的样式: 从外联样式到 Styled Components — SitePoint

2,811 阅读6分钟
原文链接: www.zcfy.cc

虽然使用React构建应用程序在许多方面已经达到了一定程度的标准化,但是样式仍然是一个有许多解决方案的领域。 每种方案都有其优点和缺点,并没有明确的最佳选择。

在这篇文章中,我会简要介绍关于React组件在Web应用程序是是如何组织样式的。当然,我也会介绍下styled-components

A cosmetics kit with various '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>
    )
  }
}

CodePen

正如我们所看到的,我们通过使用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>
    )
  }
}

CodePen

这种样式写法的主要优势是可以写原生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>
    )
  }
}

Codepen

当我们使用样式组件重构它时,我们创建一个有默认背景样式的按钮组件。 由于组件就像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>
    )
  }
}

Codepen

高级用法

样式组件提供了创建复杂高级组件的能力,我们可以使用现有的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>
    )
  }
}

Codepen

由于样式组件允许我们传递标准的DOM元素和其他组件,我们可以从基本组件中构建高级功能。

组件结构

从我们高级和基础示例中,我们可以构建一个组件结构。大多数标准的React应用程序都有一个组件目录:我们将样式组件放在styledComponents目录下。 我们的styledComponents目录包含所有组成的基本组件。 然后将这些导入到我们应用中使用的展示组件中。目录布局如下所示:

src/
  components/
    addUser.js
    styledComponents/
      basicNotification.js
      successNotification.js
      errorNotification.js

最后

正如我们在这篇文章中看到的那样,我们可以对组件样式实现的方式差别很大 - 没有一个是明确的解决方案。 这篇文章显示,styled-components推动了样式元素的实现,并根据现有方法引导我们质疑自己的思考方式。

每个包括我在内的开发人员都有自己喜欢的做事方式,根据我们正在使用的应用程序,了解不同的实现方法很好。 样式的系统和语言这些年来取得了长足的进步,毫无疑问,它们将来会进一步发展,更多发展。 这在前端开发中是非常令人激动和有趣的时刻。

你更喜欢哪种方式来给React组件写样式,并且为什么?