个性化阅读
专注于IT技术分析

样式组件:用于现代Web的CSS-in-JS库

本文概述

CSS是为文档设计的, “旧网络”应该包含这些内容。 Sass或Less这类预处理器的出现表明, 社区需要的不仅仅是CSS所提供的。随着网络应用的日趋复杂, CSS的局限性变得越来越明显, 也难以缓解。

样式化组件利用了完整的编程语言JavaScript(JavaScript)的强大功能及其作用域确定功能来帮助将代码构造为组件。这有助于避免为大型项目编写和维护CSS的常见陷阱。开发人员可以描述组件的样式, 而不会产生副作用。

有什么问题?

使用CSS的优点之一是样式与代码完全分离。这意味着开发人员和设计人员可以并行工作而不会互相干扰。

另一方面, 使用样式化的组件可以更轻松地将样式和逻辑强耦合在一起。 Max Stoiber解释了如何避免这种情况。虽然分离逻辑和表示的想法绝对不是新鲜事物, 但是在开发React组件时, 人们可能会倾向于采用捷径。例如, 很容易为验证按钮创建一个组件, 该组件可以处理单击操作以及按钮的样式。将其分为两个部分需要花费更多的精力。

容器/外观的架构

这是一个非常简单的原则。组件要么定义事物的外观, 要么管理数据和逻辑。表示组件的一个非常重要的方面是它们永远不应有任何依赖关系。他们接收道具并相应地渲染DOM(或子级)。另一方面, 容器知道数据架构(状态, redux, 通量等), 但绝不负责显示。丹·阿布拉莫夫(Dan Abramov)的文章很好地解释了这种架构。

记住SMACSS

尽管CSS的可伸缩和模块化体系结构是组织CSS的样式指南, 但基本概念却是样式组件自动遵循的基本概念。想法是将CSS分为五类:

  • 基本包含所有一般规则。
  • Layout的目的是定义结构属性以及内容的各个部分(例如, 页眉, 页脚, 侧边栏, 内容)。
  • 模块包含用于UI的各种逻辑块的子类别。
  • 状态定义修饰符类以指示元素的状态, 例如错误的字段, 禁用按钮。
  • 主题包含颜色, 字体和其他可修改的或取决于用户偏好的装饰性方面。

在使用样式化组件时保持这种分离很容易。项目通常包括某种CSS规范化或重置。这通常属于”基本”类别。你还可以定义常规字体大小, 行大小等。这可以通过常规CSS(或Sass / Less)或通过样式组件提供的injectGlobal函数来完成。

对于布局规则, 如果使用UI框架, 则它可能会定义容器类或网格系统。你可以在编写的布局组件中轻松地将这些类与自己的规则结合使用。

由于样式是直接附加到组件的, 而不是在外部文件中描述的, 因此样式组件的体系结构将自动跟随模块。基本上, 你编写的每个样式化组件都是其自己的模块。你可以编写样式代码, 而不必担心副作用。

状态将是你在组件内定义为可变规则的规则。你只需定义一个函数即可内插CSS属性的值。如果使用UI框架, 则可能还会有有用的类添加到组件中。你可能还会有CSS伪选择器规则(悬停, 焦点等)。

主题可以简单地插在组件中。将主题定义为在整个应用程序中使用的一组变量是一个好主意。你甚至可以以编程方式(使用库或手动)导出颜色, 例如处理对比度和高光。请记住, 你可以使用编程语言的全部功能!

将他们聚在一起寻求解决方案

保持它们在一起很重要, 以便于导航。我们不想按类型(表示或逻辑)来组织它们, 而是按功能来组织它们。

因此, 我们将为所有通用组件(按钮等)提供一个文件夹。其他项目应根据项目及其功能进行组织。例如, 如果我们具有用户管理功能, 则应将特定于该功能的所有组件组合在一起。

要将样式化组件的容器/表示架构应用于SMACSS方法, 我们需要一种额外的组件类型:结构。我们最终得到三种组件:样式, 结构和容器。由于样式化组件会修饰标记(或组件), 因此我们需要第三种类型的组件来构造DOM。在某些情况下, 可能允许容器组件处理子组件的结构, 但是当DOM结构变得复杂且出于视觉目的而必需时, 最好将它们分开。一个很好的例子是一个表, 其中DOM通常变得非常冗长。

示例项目

让我们构建一个小应用程序, 其中显示一些食谱来说明这些原理。我们可以开始构建Recipes组件。父组件将是控制器。它将处理状态-在这种情况下为配方列表。它还将调用API函数来获取数据。

class Recipes extends Component{
  constructor (props) {
    super(props);
    this.state = {
      recipes: []
    };
  }

  componentDidMount () {
    this.loadData()
  }

  loadData () {
    getRecipes().then(recipes => {
      this.setState({recipes})
    })
  }

  render() {
    let {recipes} = this.state

    return (
      <RecipesContainer recipes={recipes} />
    )
  }
}

它会显示配方列表, 但不需要(也不应该)知道如何做。因此, 我们渲染了另一个获取配方列表并输出DOM的组件:

class RecipesContainer extends Component{
  render() {
    let {recipes} = this.props

    return (
      <TilesContainer>
        {recipes.map(recipe => (<Recipe key={recipe.id} {...recipe}/>))}
      </TilesContainer>
    )
  }
}

实际上, 在这里, 我们要制作一个平铺网格。使实际的图块布局成为通用组件可能是一个好主意。因此, 如果将其提取出来, 我们将得到一个新的组件, 如下所示:

class TilesContainer extends Component {
  render () {
    let {children} = this.props

    return (
      <Tiles>
        {
          React.Children.map(children, (child, i) => (
            <Tile key={i}>
              {child}
            </Tile>
          ))
        }
      </Tiles>
    )
  }
}

TilesStyles.js:

export const Tiles = styled.div`
  padding: 20px 10px;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
`

export const Tile = styled.div`
  flex: 1 1 auto;
  ...
  display: flex;

  & > div {
    flex: 1 0 auto;
  }
`

请注意, 此组件纯粹是演示性的。它定义了样式, 并将收到的所有子项包装在另一个样式化的DOM元素中, 该元素定义了瓦片的外观。这是一个很好的例子, 展示了你的通用演示组件在架构上的外观。

然后, 我们需要定义配方的外观。我们需要一个容器组件来描述相对复杂的DOM并在必要时定义样式。我们最终得到以下结果:

class RecipeContainer extends Component {
  onChangeServings (e) {
    let {changeServings} = this.props
    changeServings(e.target.value)
  }

  render () {
    let {title, ingredients, instructions, time, servings} = this.props

    return (
      <Recipe>
        <Title>{title}</Title>
        <div>{time}</div>
        <div>Serving
          <input type="number" min="1" max="1000" value={servings} onChange={this.onChangeServings.bind(this)}/>
        </div>
        <Ingredients>
          {ingredients.map((ingredient, i) => (
            <Ingredient key={i} servings={servings}>
              <span className="name">{ingredient.name}</span>
              <span className="quantity">{ingredient.quantity * servings} {ingredient.unit}</span>
            </Ingredient>
          ))}
        </Ingredients>
        <div>
          {instructions.map((instruction, i) => (<p key={i}>{instruction}</p>))}
        </div>
      </Recipe>
    )
  }
}

请注意, 该容器会生成一些DOM, 但这是它包含的唯一逻辑。请记住, 你可以定义嵌套样式, 因此无需为每个需要样式的标签制作一个样式元素。这就是我们在这里所做的配料项目的名称和数量。当然, 我们可以进一步拆分它, 并为一种成分创建一个新组件。取决于项目的复杂程度, 取决于你来确定粒度。在这种情况下, 它只是与RecipeStyles文件中的其余部分一起定义的样式化组件:

export const Recipe = styled.div`
  background-color: ${theme('colors.background-highlight')};
`;

export const Title = styled.div`
  font-weight: bold;
`

export const Ingredients = styled.ul`
  margin: 5px 0;
`

export const Ingredient = styled.li`
  & .name {
    ...
  }

  & .quantity {
    ...
  }
`

出于本练习的目的, 我使用了ThemeProvider。它将主题注入样式化组件的道具中。你可以简单地将其用作颜色:$ {props => props.theme.core_color}, 我只是使用一个小的包装来防止主题中缺少属性:

const theme = (key) => (prop) => _.get(prop.theme, key) || console.warn('missing key', key)

你也可以在模块中定义自己的常量, 而改用这些常量。例如:color:$ {styleConstants.core_color}

优点

使用样式化组件的好处是你可以随意使用它。你可以使用自己喜欢的UI框架, 并在其之上添加样式化组件。这也意味着你可以轻松地按组件迁移现有项目组件。你可以选择使用标准CSS为大多数布局设置样式, 并且仅将样式组件用于可重用组件。

缺点

设计师/样式集成商将需要学习非常基本的JavaScript来处理变量, 并使用它们代替Sass / Less。

他们还必须学会在项目结构中导航, 尽管我会争辩说, 在该组件的文件夹中查找该组件的样式比必须找到包含需要修改的规则的正确CSS / Sass / Less文件要容易得多。

如果需要语法高亮显示, 掉毛等, 他们还需要稍稍更改工具。一个不错的起点是使用此Atom插件和此babel插件。

赞(0)
未经允许不得转载:srcmini » 样式组件:用于现代Web的CSS-in-JS库

评论 抢沙发

评论前必须登录!