w3ctech

React 初学者教程 4: 在 React 中设置样式

长期以来,我们都是用 CSS 格式化 HTML 内容。用 CSS,可以很好地分离内容和表现形式。CSS 的选择器语法给我们很大的灵活性,来选择哪个要格式化,哪个会略过。你甚至找不到太多的问题来憎恨 CSS 的层叠。

好了,不要告诉 React 这些。React不会积极地憎恨 CSS,但是它格式化内容时,有一个不同的视角。正如迄今为止我们所看到的,React 的核心思想之一是让应用程序的界面部分可以自包含和重用。这就是为什么组成界面的 HTML 元素和 JavaScript 装到同一个称为组件的桶中的原因。在上一个教程中我们已经品尝过组件。

如何格式化 HTML 元素?样式应该放在哪里?你可能猜到我们会在哪里处理这点。如果把样式放在另一个地方,我们就没法有一个自包含的 UI 块。这就是为什么 React 鼓励我们把样式与 HTML 和 JavaScript 放在一起的原因。在本教程中,我们将学习这种神秘的,可能是骇人听闻的格式化内容的方法。当然,我们还会看看如何使用 CSS。React 为两种方式都提供了空间,即使 React 可能有点点不这样认为 :P

显示一些元音字母

为学习如何格式化 React 内容,我们就用一个示例,这个示例只是在页面上显示元音字母。首先,我们需要一个能放置 React 内容的空白 HTML 页面。如果你还没有这个页面,那就随便用如下的标记好了:

<!DOCTYPE html>

<html>
  <head>
    <title>Styling in React</title>
    <script src="<https://fb.me/react-15.0.0-rc.2.js>"></script>
    <script src="<https://fb.me/react-dom-15.0.0-rc.2.js>"></script>
    <script src="<https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js>"></script>
    <style>
      #container {
        padding: 50px;
        background-color: #FFF;
      }
    </style>
  </head>
  <body>
    <div id="container"></div>
  </body>
</html>

所有这些标记实现的是加载 React 和 Babel 库,并指定一个 id 值为 containerdiv。要显示元音字幕,我们打算添加一些特定的 React 代码。在 container div 元素下,添加如下代码:

<script type="text/babel">
  var Letter = React.createClass({
    render: function() {
      return (
        <div>
          {this.props.children}
        </div>
      );
    }
  });

  var destination = document.querySelector("#container");

  ReactDOM.render(
    <div>
      <Letter>A</Letter>
      <Letter>E</Letter>
      <Letter>I</Letter>
      <Letter>O</Letter>
      <Letter>U</Letter>
    </div>,
    destination
  );
</script>

根据上章所学,这里也没什么神秘的。我们创建了一个名为 Letter 的组件,负责将元音字母放在一个 div 元素内。这些代码通过一个 script 标记放在 HTML 中,script 标记的 type 属性被指定为 text/babel,这样 Babel 就知道要做什么。

如果预览这个页面,我们会看到如下的结果:

现在不要着急,过一会儿我们会让页面看起来不那么恶心。在对这些字符做点动作之后,我们会看到界面变成这样子:

我们的元音字幕会用黄色背景包起来,水平对齐,并且是等宽字体。我们来看看如何分别用 CSS 以及 React 的新奇方法实现。

用 CSS 格式化 React 内容

用 CSS 来样式化 React 内容实际上就像我们料想的那样简单。因为 React 最终输出的还是普通 HTML 标记,所以所有我们以前学过的样式化 HTML 的 CSS 技巧依然可以应用。这里只有一些次要的事情要记住。

理解生成的 HTML

在开始用 CSS 前,我们首先切身体会一下 React 输出的 HTML 会是什么样子。通过查看在 render 方法内定义的 JSX,我们可以很容易搞明白。上层的父 render 方法是 ReactDOM 提供的,它看起来是这样的:

<div>
  <Letter>A</Letter>
  <Letter>E</Letter>
  <Letter>I</Letter>
  <Letter>O</Letter>
  <Letter>U</Letter>
</div>

我们有多个被 div 包裹的 Letter 组件。这里也没什么令人激动的。Letter 组件内的 render 方法也没什么太大的不同:

<div>
  {this.props.children}
</div>

我们可以看到,每个元音字母都被包含在一套 div 标记中。如果你一定要自己玩这个(比如,在浏览器中预览我们的示例),最终的 DOM 结构看起来会是这样的:

现在我们先忽略不同的 data-reactid 属性,先注意浏览器中你看到的 HTML。我们所有的只是我们前面看到的 render 方法中各种 JSX 片段的一个 HTML 化扩展。

用 CSS 方式格式化

一旦我们理解了想要样式化的 HTML 事情安排,最难的部分就完成了。现在就可以为想要设置的元素定义样式选择器,指定属性。要影响最内部的 div 元素,在 style 标记中添加如下内容:

div div div {
  padding: 10px;
  margin: 10px;
  background-color: #ffde00;
  color: #333;
  display: inline-block;
  font-family: monospace;
  font-size: 32px;
  text-align: center;
}

div div div 选择器会确保我们格式化正确的元素。最终的结果将是,我们的元音字母被格式化为最初我们看到的那样子。当然,div div div 样式选择器看起来有点奇怪,是不是?它太通用了。如果应用中有多个三个 div 元素(这也很常见),那么就可能格式化了错误的元素。

解决方法是给最内部的 div 元素一个 classletter。这里是 JSX 与 HTML 不同的地方。在代码中作出如下修改:

var Letter = React.createClass({
  render: function() {
    return (
      <div className="letter">
        {this.props.children}
      </div>
    );
  }
});

注意这里我们是用 className 属性而不是 class 属性来指定 class 值。原因是 class 是 JavaScript 中的一个特殊关键字。如果这还说不通为什么这是很重要的,那么现在不要担心。我们会在后面涉及。

不管怎样,在给 divclassName 属性设置值为 letter 后,我们还有一件事情要做。修改 CSS 选择器,让选择 div 元素更清晰:

.letter {
  padding: 10px;
  margin: 10px;
  background-color: #ffde00;
  color: #333;
  display: inline-block;
  font-family: monospace;
  font-size: 32px;
  text-align: center;
}

正如你所见,在基于 React 的应用中,用 CSS 对元素进行格式化是一个相当可行的方法。在下一节,我们将看看如何使用 React 喜欢的方法来格式化内容。

用 React 的方式格式化内容

React 偏爱用一种不使用 CSS 的行内(inline)方法格式化内容。这种方法看起来是有点奇怪,但是它是设计用来帮助让界面更可重用。目标是让我们的组件放在一个黑盒中,所有与 UI 外观和使用相关的东西都放在一个地方。

继续前面的示例,删除掉 .letter 样式规则。这样做了之后,如果在浏览器中预览应用,元音字母又会回到没有样式的状态。为保持完整性,我们还应该从 Letter 组件的 render 函数中删除 className 声明。让我们的标记包含不会用到的东西是没有意义的。

现在,我们的 Letter 组件回到了它的初始状态:

var Letter = React.createClass({
  render: function() {
    return (
      <div>{this.props.children}</div>
    );
  }
});

在组件中指定样式的方式是,通过定义一个对象,该对象的内容是 CSS 属性及其值。有了这个对象后,我们就可以用 style 属性,把该对象赋值给想要样式化的 JSX 元素。一旦我们自己执行这两个步骤,这就会变得更有意义,所以我们用这个方法来格式化 Letter 组件的输出。

创建一个 Style 对象

我们赶快行动,定义一个对象来包含要应用的样式:

var Letter = React.createClass({
  render: function() {
    var letterStyle = {
      padding: 10,
      margin: 10,
      backgroundColor: "#ffde00",
      color: "#333",
      display: "inline-block",
      fontFamily: "monospace",
      fontSize: "32",
      textAlign: "center"
    };
    return (
      <div>{this.props.children}</div>
    );
  }
});

这样我们就有了一个 letterStyle 对象,它里面的属性只是 CSS 属性名以及属性值。如果你以前从来没有在 JavaScript 中定义 CSS 属性(例如,通过设置 object.style),那么转换 CSS 属性为对 JavaScript 友好的东西的公式相当简单:

  1. 单个单词的 CSS 属性(比如 padding, margin, color)保持不变。
  2. 带有短横线的多个单词的 CSS 属性(比如 background-color, font-family, border-radius)变成去掉横线,并且第二个单词的首字母大写的驼峰规则命名的单词。 例如,background-color 变成 backgroundColor, font-family 变成 fontFamily, border-radius 变成 borderRadius

我们的 letterStyle 对象及其属性差不多就是直接将前面看到的 .letter 样式规则翻译为 JavaScript 。现在剩下来的事情就是将这个对象赋值给要格式化的元素。

实际格式化我们的内容

现在我们已经有了包含要应用的样式的对象,剩下的事情很简单。找到要应用样式的元素,设置该元素的 style 属性来引用该对象。在我们的例子中,这个元素就是被 Letter 组件的 render 函数返回的 div 元素。

看看下面代码中的高亮度行,看看我们是如何做的:

var Letter = React.createClass({
  render: function() {
    var letterStyle = {
      padding: 10,
      margin: 10,
      backgroundColor: "#ffde00",
      color: "#333",
      display: "inline-block",
      fontFamily: "monospace",
      fontSize: "32",
      textAlign: "center"
    };

    return (
      <div style={letterStyle}>
        {this.props.children}
      </div>
    );
  }
});

我们的对象叫 letterStyle,它的内容在大括号内指定的,让 React 知道如何计算表达式的。就是这么回事。继续,在浏览器中执行示例,确保所有一切都正确,所有元音字母被正确格式化。

为额外校验一下,如果你使用浏览器开发工具查看元音字母之一的样式,就会看到样式实际上是内联应用的:

虽然这并不奇怪,但是这也许让我们中那些习惯在样式规则内格式化元素的人有点难接受。正如他们所说:时代在改变

可以省略 “px” 后缀

当通过程序设置样式时,处理需要一个像素值为后缀的数字是一个痛苦。为了生成这些值,需要在数字上执行一些字符串连接操作来添加一个 px。为了将像素值转换回数字,又需要解析出 px。虽然这并不是特别复杂或者费时,但是会让人心烦意乱。

为了做到这点,React 允许我们省略一些 CSS 属性的 px 后缀。如果你还记得,我们的 letterStyle 对象看起来是这样的:

var letterStyle = {
  padding: 10,
  margin: 10,
  backgroundColor: "#ffde00",
  color: "#333",
  display: "inline-block",
  fontFamily: "monospace",
  fontSize: "32",
  textAlign: "center"
};

注意有些属性值为数字值的属性,比如 paddingmarginfontSize,我们完全不需要指定 px 后缀。这是因为在运行期,React 会自动添加上 px 后缀。

而 React 不会自动添加像素后缀的与数字相关的属性包括:animationIterationCount, boxFlex, boxFlexGroup, boxOrdinalGroup, columnCount, fillOpacity, flex, flexGrow, flexPositive, flexShrink, flexNegative, flexOrder, fontWeight, lineClamp, lineHeight, opacity, order, orphans, stopOpacity, strokeDashoffset, strokeOpacity, strokeWidth, tabSize, widows, zIndex, zoom。我希望我可以告诉你,我是不会记住这些信息的,我只是引用这篇文章!请鼓掌 :P

虽然像素值很多时候是不错的,但是也许你会想用百分比、ems、vh 等来表示值。对于这些非像素值,你依然要手动添加上后缀。

让背景颜色可定制

在结束在 React 如何使用样式之前,我们要做最后一件事情。通过将样式定义在 JSX 附近相同位置,我们可以让多个样式值很容易通过父元素来定制(父元素即组件的消费者)。我们来看看如何实现。

现在,所有元音字母都是黄色背景色。如果我们可以在每个字符声明的时候分别指定其背景色,是不是很酷呢?要实现这功能,在 ReactDOM.render 方法中,首先添加一个 bgcolor 属性,并指定一些颜色:

ReactDOM.render(
  <div>
    <Letter bgcolor="#58B3FF">A</Letter>
    <Letter bgcolor="#FF605F">E</Letter>
    <Letter bgcolor="#FFD52E">I</Letter>
    <Letter bgcolor="#49DD8E">O</Letter>
    <Letter bgcolor="#AE99FF">U</Letter>
  </div>,
  destination
);

下一步,我们需要用这个属性。在 letterStyle 对象中,设置 backgroundColorthis.props.bgColor

var letterStyle = {
  padding: 10,
  margin: 10,
  backgroundColor: this.props.bgcolor,
  color: "#333",
  display: "inline-block",
  fontFamily: "monospace",
  fontSize: "32",
  textAlign: "center"
};

这将确保 backgroundColor 的值是从我们通过作为 Letter 声明一部分的 bgColor 属性中推断出来的。如果在浏览器中预览,我们会看到我们的元音字母现在完全是甜蜜的背景色:

我们刚才所做的,是用普通 CSS 很难实现的。后面我们在学习那些内容会根据状态或者用户交互而改变的组件时,我们会看到更多的这样用 React 方式格式化内容的示例有更多好的优点。

总结

随着我们更加深入学习 React,你会看到好几个 React 的特有的功能,与过去在 Web 上我们被告诉的正确方式有很大的不同。在本教程中,我们看到 React 使用在 JavaScript 中的行内样式来格式化内容,而不是用 CSS 样式规则。更早一点,我们看到 JSX,以及如何让整个 UI 在 JavaScript 中用看起来像 HTML 的 XML 类似的语法声明。

在所有这些示例中,如果你透过表面往更深层次看,React 偏离传统智慧的原因是非常有意义的。创建带有很复杂用户界面的应用程序需要解决问题的新方法。在处理网页和文档时可能有非常有意义的 HTML、CSS 和 JavaScript 技术,在 Web app 世界里可能并不合适。

所以说,你应该挑选最符合你条件的技术。虽然我偏向用 React 的方式解决 UI 开发的问题,但是我也会尽力突出可选的或者惯用的方法。正如前面我们看到的,在 React 内容中用 CSS 样式规则是完全可以的,只要你作出决定,知道这样做你会获得什么以及失去什么。

w3ctech微信

扫码关注w3ctech微信公众号

共收到0条回复