CSSの進化:CSS、SASS、BEM、CSSモジュールからスタイル付きコンポーネントへ





インターネットの歴史の最初から、サイトのスタイルが必要でした。 長年にわたり、独自のペースで開発されたCSSがこのために役立ちました。 そして、ここでその開発の歴史を見ていきます。



私は誰もがこの定義に同意すると思う:CSSはマークアップ言語で書かれドキュメントのプレゼンテーションを記述するために使用されます 。 また、CSSが長年にわたってかなり強力なツールになり、チームを使用するために追加のツールが必要であることも誰にとってもニュースではありません。



ワイルドCSS



1990年代、私たちは「素晴らしい」インターフェイスを作成するのが好きでした。すごい要素が最も重要でした。 当時はインラインスタイルが重視されていましたが、ページの要素が異なって見えるかどうかは気にしませんでした。 Webページはかわいいおもちゃで、クールなgif、忍び寄るライン、その他の悪夢のような(しかし印象的な)要素で飽和し、訪問者の注意を引き付けようとしました。







その後、動的サイトの作成を開始しましたが、CSSは無法の要塞のままでした。各開発者は、CSSの作成方法について独自のアイデアを持っていました。 誰かが特異性に苦労し、新しいコードが登場すると視覚的な退行につながりました。 重要なのは、インターフェイス要素が特定の方法で見えるように、石に意志のシンボルを彫りたいということです。 しかし、すぐに気付きました:









プロジェクトの規模と複雑さが増し、開発チームが成長するにつれて、これらの手法はすべて、ますます明白で大きな問題に変わりました。 したがって、スタイルの適用におけるパターンの欠如は、CSSを使用する正しい方法を見つけようとしている経験豊富で経験の浅い開発者にとって主要な障害の1つになりました。 最終的に、正しい方法と間違った方法がないことに気付きました。 私たちはすべてをまともに見えるようにしようとしました。









SASSによる救助



SASSはCSSを、 ネスト、変数、ミックスイン、拡張、ロジックをスタイルシートに実装する前処理エンジンとして提示される、適切なプログラミング言語に変えました。 そのため、CSSファイルをより適切に整理できます。また、CSSコードの大きな部分を小さなファイルに分解する方法がいくつかあります。 当時、これは大きな革新でした。



原則は次のとおりです。CSSコードを取得して前処理し、ファイルを一般的なCSSパッケージにコンパイルします。 かっこいい それほど多くはありません。 しばらくして、SASSが戦略なしで最高の技術を適用しなければ、SASSが解決する以上の問題をもたらすことが明らかになりました。



突然、開発者はプリプロセッサが何をしているのかを詳しく調べることをやめ、特定性を打ち負かすためにネストにゆっくりと依存し始めました。 しかし、これにより、コンパイルされたスタイルページのサイズが急激に増加しました。



BEMが表示されるまで...



BEMとコンポーネントの概念



BEMは新鮮な空気の息吹でした。 彼は、再利用性とコンポーネントについてもっと考えさせてくれました。 本質的に、このテクノロジーはセマンティクスを新しいレベルに引き上げました。 これで、classNameが一意であり、単純なBlock、Element、Modifier規則を使用することで、特定の表示のリスクが軽減されることが確認できました。



例を見てみましょう:



<body class="scenery"> <section class="scenery__sky"> <div class="sky [sky--dusk / sky--daytime] [sky--foggy]"> <div class="sky__clouds"></div> <div class="sky__sun"></div> </div> </section> <section class="scenery__ground"></section> <section class="scenery__people"></section> </body>
      
      





マークアップを分析すると、すぐにBEM契約の成果が表示されます。 コードには、 .scenery



.sky



2つの明示的なブロックがあります。 それぞれに独自のブロックがあります。 たとえば、霧、昼、または夕焼けはすべて同じ要素に適用できる異なる特性であるため、 sky



のみ修飾子があります。



より良い分析のために、いくつかの擬似コードを含む付随するCSSを見てください:



 // Block .scenery { //Elements &__sky { fill: screen; } &__ground { float: bottom; } &__people { float: center; } } //Block .sky { background: dusk; // Elements &__clouds { type: distant; } &__sun { strength: .025; } // Modifiers &--dusk { background: dusk; .sky__clouds { type: distant; } .sky__sun { strength: .025; } } &--daytime { background: daylight; .sky__clouds { type: fluffy; float: center; } .sky__sun { strength: .7; align: center; float: top; } } }
      
      





BEMの動作を完全に理解したい場合は、友人や同僚が書いた記事を読むことをお勧めします。



BEMは、#reusabilityFtw固有のコンポーネントの作成に適しています。 このアプローチにより、新しいスタイルが古いスタイルシートに導入されるにつれて、いくつかのパターンがより明確になりました。



しかし同時に、新しい問題が発生しました:





CSSモジュールとローカル表示スペース



SASSもBEMもいくつかの問題を解決できませんでした。 たとえば、言語のロジックには、真のカプセル化の概念はありません。 したがって、クラス名を選択するタスクは開発者に任されています。 問題は、合意ではなくツールの助けを借りて解決できると感じました。



これは、CSSモジュールが実行したこととまったく同じです。ローカルで定義された各スタイルの動的クラス名の作成に基づいています。 これにより、新しいCSSプロパティの導入により生じた視覚的な退行を取り除くことができ、すべてのスタイルが正しくカプセル化されました。



CSSモジュールはすぐにReactエコシステムで人気を博し、今日では多くのプロジェクトで使用されています。 それらには長所と短所がありますが、全体としてこれは優れた有用なパラダイムです。



ただし...モジュール自体は主要なCSSの問題を解決しません。スタイル定義をローカライズする方法を示しているだけです。BEMを自動化するスマートな方法で、クラス名を処理する必要がなくなります (少なくとも少なくします)。



しかし、モジュールは、予測可能なスタイルの優れたアーキテクチャの必要性を軽減するものではなく、拡張と再利用が容易であり、管理に最小限の労力しか必要ありません。



ローカルCSSは次のようになります。



 @import '~tools/theme'; :local(.root) { border: 1px solid; font-family: inherit; font-size: 12px; color: inherit; background: none; cursor: pointer; display: inline-block; text-transform: uppercase; letter-spacing: 0; font-weight: 700; outline: none; position: relative; transition: all 0.3s; text-transform: uppercase; padding: 10px 20px; margin: 0; border-radius: 3px; text-align: center; } @mixin button($bg-color, $font-color) { background: $bg-color; color: $font-color; border-color: $font-color; &:focus { border-color: $font-color; background: $bg-color; color: $font-color; } &:hover { color: $font-color; background: lighten($bg-color, 20%); } &:active { background: lighten($bg-color, 30%); top: 2px; } } :local(.primary) { @include button($color-primary, $color-white) } :local(.secondary) { @include button($color-white, $color-primary) }
      
      





これは単なるCSSであり、主な違いは、 :local



を追加したすべてのclassName



が次のような一意のクラス名を生成することです。



 .appcomponentsbutton–__root — 3vvFf {}
      
      





生成された識別子は、クエリパラメーターlocalIdentName



を使用して構成できます。 例: css–loader?localIdentName=[path][name]–––[local]–––[hash:base64:5]



デバッグを容易にします。



ローカルCSSモジュールは、単純なアイデアに基づいています。 同じ名前が使用されている場合でも、他と競合しない一意のclassName



を生成することにより、BEM表記を自動化する方法です。 とても便利です。



スタイル付きコンポーネントを使用したJavaScriptによる完全なCSSインジェクション



スタイル付きコンポーネントは、ラッパーのように機能する視覚的なプリミティブです。 これらは、子コンポーネントをスタイル付きコンポーネントでラップする特定のHTMLタグに関連付けることができます。



このコードは、アイデアを理解するのに役立ちます。



 import React from "react" import styled from "styled-components" // Simple form component const Input = styled.input` background: green ` const FormWrapper = () => <Input placeholder="hola" /> // What this compiles to: <input placeholder="hola" class="dxLjPX">Send</input>
      
      





非常に簡単です。styled-componentsは、テンプレートリテラル表記を使用してCSSプロパティを記述します。 ES6とCSSの機能を組み合わせることで、開発チームが成功したようです。



Styled-componentsは、非常にシンプルで再利用可能なパターンを提供し、インターフェイスを機能および構造コンポーネントから完全に分離します。 ブラウザーでHTMLとして、またはReact Nativeがネイティブで使用されるネイティブタグにアクセスできるAPIが作成されます。



カスタムプロパティ(または修飾子)がスタイル付きコンポーネントに渡される方法は次のとおりです。



 import styled from "styled-components" const Sky = styled.section` ${props => props.dusk && 'background-color: dusk' } ${props => props.day && 'background-color: white' } ${props => props.night && 'background-color: black' } `; // You can use it like so: <Sky dusk /> <Sky day /> <Sky night />
      
      





ご覧のとおり、プロパティは突然各コンポーネントが受け取る修飾子になり、異なるCSS文字列を取得するために処理できます。 これにより、JSのすべての機能を使用してスタイルを処理でき、同時に一貫性が保たれ、再利用の準備が整います。



メインインターフェイスは誰でも再利用できます



CSSモジュールやスタイル付きコンポーネントだけが理想的なソリューションではないことがすぐに明らかになりました。 これらすべてが効率的に機能し、拡張するには、特定のパターンが必要です。 このようなパターンは、コンポーネントとは何かの定義と、ロジックからの完全な分離から生じました。 これにより、スタイルのみを目的とするコアコンポーネントを作成できました。



CSSモジュールを使用したこのようなコンポーネントの実装例:



 import React from "react"; import classNames from "classnames"; import styles from "./styles"; const Button = (props) => { const { className, children, theme, tag, ...rest } = props; const CustomTag = `${tag}`; return ( <CustomTag { ...rest } className={ classNames(styles.root, theme, className) }> { children } </CustomTag> ); }; Button.theme = { secondary: styles.secondary, primary: styles.primary }; Button.defaultProps = { theme: Button.theme.primary, tag: "button" }; Button.displayName = Button.name; Button.propTypes = { theme: React.PropTypes.string, tag: React.PropTypes.string, className: React.PropTypes.string, children: React.PropTypes.oneOfType([ React.PropTypes.string, React.PropTypes.element, React.PropTypes.arrayOf(React.PropTypes.element) ]) }; export default Button;
      
      





ここで、コンポーネントは子コンポーネントにバインドされたプロパティを取得します。 つまり、ラッパーコンポーネントはすべてのプロパティを子コンポーネントに渡します。



これで、コンポーネントを次のように適用できます。



 import React from "react" import Button from "components/core/button" const = Component = () => <Button theme={ Button.theme.secondary }>Some Button</Button> export default Component
      
      





スタイル付きコンポーネントを使用した完全なボタン実装の同様の例を示します。



 import styled from "styled-components"; import { theme } from "ui"; const { color, font, radius, transition } = theme; export const Button = styled.button` background-color: ${color.ghost}; border: none; appearance: none; user-select: none; border-radius: ${radius}; color: ${color.base} cursor: pointer; display: inline-block; font-family: inherit; font-size: ${font.base}; font-weight: bold; outline: none; position: relative; text-align: center; text-transform: uppercase; transition: transorm ${transition}, opacity ${transition}; white-space: nowrap; width: ${props => props.width ? props.width : "auto"}; &:hover, &:focus { outline: none; } &:hover { color: ${color.silver}; opacity: 0.8; border-bottom: 3px solid rgba(0,0,0,0.2); } &:active { border-bottom: 1px solid rgba(0,0,0,0.2); transform: translateY(2px); opacity: 0.95; } ${props => props.disabled && ` background-color: ${color.ghost}; opacity: ${0.4}; pointer-events: none; cursor: not-allowed; `} ${props => props.primary && ` background-color: ${color.primary}; color: ${color.white}; border-color: ${color.primary}; &:hover, &:active { background-color: ${color.primary}; color: ${color.white}; } `} ${props => props.secondary && ` background-color: ${color.secondary}; color: ${color.white}; border-color: ${color.secondary}; &:hover, &:active { background-color: ${color.secondary}; color: ${color.white}; } `} `;
      
      





興味深い点:コンポーネントは完全に愚かで、親コンポーネントにバインドされたCSSプロパティのラッパーとしてのみ機能します。 このアプローチには次の利点があります。



これにより、アプリケーション内のすべてのインターフェイスの一貫性を保ちながら、必要に応じて変更できるベースインターフェイスのAPIを記述することができます。



したがって、デザインの作成を実装から完全に分離できます。 必要に応じて、それらは同時に流れます。1人の開発者が機能の実装に従事し、もう1人がインターフェースを洗練します。これはすべて責任を完全に分離して行われます。



いいですね。 このパターンに従う必要があるようです。 彼と一緒に、他の有用な解決策を探し始めました。



不動産受取人



これらの関数は、コンポーネントに渡されるプロパティをリッスンします。 本当に、あらゆるコンポーネントの再利用と権限付与の聖杯です。 これは、修飾子を継承する方法と見なすことができます。 ここに私が意味するものがあります:



 // Prop passing Shorthands for Styled-components export const borderProps = props => css` ${props.borderBottom && `border-bottom: ${props.borderWidth || "1px"} solid ${color.border}`}; ${props.borderTop && `border-top: ${props.borderWidth || "1px"} solid ${color.border}`}; ${props.borderLeft && `border-left: ${props.borderWidth || "1px"} solid ${color.border}`}; ${props.borderRight && `border-right: ${props.borderWidth || "1px"} solid ${color.border}`}; `; export const marginProps = props => css` ${props.marginBottom && `margin-bottom: ${typeof (props.marginBottom) === "string" ? props.marginBottom : "1em"}`}; ${props.marginTop && `margin-top: ${typeof (props.marginTop) === "string" ? props.marginTop : "1em"}`}; ${props.marginLeft && `margin-left: ${typeof (props.marginLeft) === "string" ? props.marginLeft : "1em"}`}; ${props.marginRight && `margin-right: ${typeof (props.marginRight) === "string" ? props.marginRight : "1em"}`}; ${props.margin && `margin: ${typeof (props.margin) === "string" ? props.margin : "1em"}`}; ${props.marginVertical && ` margin-top: ${typeof (props.marginVertical) === "string" ? props.marginVertical : "1em"} margin-bottom: ${typeof (props.marginVertical) === "string" ? props.marginVertical : "1em"} `}; ${props.marginHorizontal && ` margin-left: ${typeof (props.marginHorizontal) === "string" ? props.marginHorizontal : "1em"} margin-right: ${typeof (props.marginHorizontal) === "string" ? props.marginHorizontal : "1em"} `}; `; // An example of how you can use it with your components const SomeDiv = styled.div` ${borderProps} ${marginProps} ` // This lets you pass all borderProps to the component like so: <SomeDiv borderTop borderBottom borderLeft borderRight marginVertical>
      
      





プロパティ受信者の例



これにより、特定のコンポーネントごとに境界線をハードコーディングする必要がなくなり、時間を大幅に節約できます。



プレースホルダー/ Mixinのような機能



スタイル付きコンポーネントでは、JSの可能性を最大限に活用して、関数がプロパティの単なる受信者ではなく、異なるコンポーネントがコードを共有できるようにすることができます。



 // Mixin like functionality const textInput = props => ` color: ${props.error ? color.white : color.base}; background-color: ${props.error ? color.alert : color.white}; `; export const Input = styled.input` ${textInput} `; export const Textarea = styled.textarea` ${textInput}; height: ${props => props.height ? props.height : '130px'} resize: none; overflow: auto; `;
      
      





レイアウトコンポーネント



アプリケーションで作業する場合、まずインターフェース要素のレイアウトレイアウトが必要であることがわかりました。 したがって、この問題の解決に役立つコンポーネントを特定しました。 一部の開発者(CSSポジショニング技術に精通していない)は、構造の作成に多くの時間を費やすことが多いため、非常に便利です。 そのようなコンポーネントの例を次に示します。



 import styled from "styled-components"; import { theme, borderProps, sizeProps, backgroundColorProps, marginProps } from "ui"; const { color, font, topbar, gutter } = theme; export const Panel = styled.article` ${marginProps} padding: 1em; background: white; color: ${color.black}; font-size: ${font.base}; font-weight: 300; ${props => !props.noborder && `border: 1px solid ${color.border}`}; width: ${props => props.width ? props.width : "100%"}; ${props => borderProps(props)} transition: transform 300ms ease-in-out, box-shadow 300ms ease-in-out, margin 300ms ease-in-out; box-shadow: 0 3px 3px rgba(0,0,0,0.1); ${props => props.dark && ` color: ${color.white}; background-color: ${color.black}; `} &:hover { transform: translateY(-5px); box-shadow: 0 6px 3px rgba(0,0,0,0.1); } `; export const ScrollView = styled.section` overflow: hidden; font-family: ${font.family}; -webkit-overflow-scrolling: touch; overflow-y: auto; ${props => props.horizontal && ` white-space: nowrap; overflow-x: auto; overflow-y: hidden; ` } ${props => sizeProps(props)} `; export const MainContent = styled(ScrollView)` position: absolute; top: ${props => props.topbar ? topbar.height : 0}; right: 0; left: 0; bottom: 0; font-size: ${font.base}; padding: ${gutter} 3em; ${props => props.bg && ` background-color: ${props.bg}; `} `; export const Slide = styled.section` ${backgroundColorProps} font-weight: 400; flex: 1; height: ${props => props.height ? props.height : "100%"}; width: ${props => props.width ? props.width : "100%"}; justify-content: center; flex-direction: column; align-items: center; text-align: center; display: flex; font-size: 3em; color: ${color.white}; `; export const App = styled.div` *, & { box-sizing: border-box; } `;
      
      





<ScrollView />



コンポーネントは、幅と高さ、および下に表示されるスクロールバーの水平プロパティをプロパティとして取得します。



補助コンポーネント



彼らは私たちの生活を楽にし、私たちが積極的に再利用に従事できるようにします。 ここでは、一般的に使用されるすべてのパターンを保存します。 私に役立つヘルパーコンポーネントをいくつか紹介します。



 import styled, { css } from "styled-components"; import { borderProps, marginProps, backgroundColorProps, paddingProps, alignmentProps, positioningProps, sizeProps, spacingProps, theme } from "ui"; const { screenSizes } = theme; export const overlay = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,0.5); `; // You can use this like ${media.phone`width: 100%`} export const media = Object.keys(screenSizes).reduce((accumulator, label) => { const acc = accumulator; acc[label] = (...args) => css` @media (max-width: ${screenSizes[label]}em) { ${css(...args)} } `; return acc; }, {}); // Spacing export const Padder = styled.section` padding: ${props => props.amount ? props.amount : "2em"}; `; export const Spacer = styled.div` ${spacingProps} `; // Alignment export const Center = styled.div` ${borderProps} ${marginProps} ${backgroundColorProps} ${paddingProps} ${alignmentProps} ${positioningProps} ${sizeProps} text-align: center; margin: 0 auto; `; // Positioning export const Relative = styled.div` ${props => borderProps(props)}; position: relative; `; export const Absolute = styled.div` ${props => marginProps(props)}; ${props => alignmentProps(props)}; ${props => borderProps(props)}; position: absolute; ${props => props.right && `right: ${props.padded ? "1em" : "0"}; `} ${props => props.left && `left: ${props.padded ? "1em" : "0"}`}; ${props => props.top && `top: ${props.padded ? "1em" : "0"}`}; ${props => props.bottom && `bottom: ${props.padded ? "1em" : "0"}`}; `; // Patterns export const Collapsable = styled.section` opacity: 1; display: flex; flex-direction: column; ${props => props.animate && ` transition: transform 300ms linear, opacity 300ms ease-in, width 200ms ease-in, max-height 200ms ease-in 200ms; max-height: 9999px; transform: scale(1); transform-origin: 100% 100%; ${props.collapsed && ` transform: scale(0); transition: transform 300ms ease-out, opacity 300ms ease-out, width 300ms ease-out 600ms; `} `} ${props => props.collapsed && ` opacity: 0; max-height: 0; `} `; export const Ellipsis = styled.div` max-width: ${props => props.maxWidth ? props.maxWidth : "100%"}; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; `; export const Circle = styled.span` ${backgroundColorProps} display: inline-block; border-radius: 50%; padding: ${props => props.padding || '10px'}; `; export const Hidden = styled.div` display: none; `;
      
      





テーマ



テーマは、アプリケーション全体で再利用できる真実の値の単一のソースです。 カラーパレットや一般的なスタイルなどを保存すると便利です。



 export const theme = { color: { primary: "#47C51D", secondary: '#53C1DE', white: "#FFF", black: "#222", border: "rgba(0,0,0,0.1)", base: "rgba(0,0,0,0.4)", alert: '#FF4258', success: 'mediumseagreen', info: '#4C98E6', link: '#41bbe1' }, icon: { color: "gray", size: "15px" }, font: { family: ` -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'`, base: '13px', small: '11px', xsmall: '9px', large: '20px', xlarge: '30px', xxlarge: '50px', }, headings: { family: 'Helvetica Neue', }, gutter: '2em', transition: '300ms ease-in-out' }; export default theme;
      
      





メリット





短所





おわりに



使用するテクノロジー(SASS、BEM、CSSモジュール、またはスタイル付きコンポーネント)にかかわらず、開発者はシステムの新しい可動部分を壊したり導入したりすることなく、コードベースを直感的に開発できます。 。



このアプローチは適切なスケーリングに必要であり、純粋なCSSとBEMが使用されている場合でも実現できます。 各実装に必要な作業量とLOCがすべてです。 一般に、スタイル付きコンポーネントは、ほとんどのReactプロジェクトに適したソリューションと呼ぶことができます。 まだ積極的にテストする必要がありますが、プロジェクトは有望に見えます。



All Articles