ホームページ >ウェブフロントエンド >CSSチュートリアル >すべてにコンポーネントが必要なわけではない

すべてにコンポーネントが必要なわけではない

DDD
DDDオリジナル
2024-11-26 03:32:12268ブラウズ

Not Everything Needs a Component

2000 年代初頭に、多くの div 要素を含む Web ページのコードを作成する行為を指す新しい用語 Divitis が作られました。意味のあるセマンティック HTML 要素の場所。これは、プログレッシブ エンハンスメント手法の枠組み内で HTML のセマンティクスに対する意識を高める取り組みの一環でした。

20 年前に遡ります - 私はウェブ開発者に影響を与える新たな症候群を目撃しました。これを私はコンポーネント炎と呼んでいます。これが私のでっち上げた定義です:

コンポーネント炎: よりシンプルで再利用可能なソリューションの代わりに、UI のあらゆる側面に対してコンポーネントを作成する実践。

コンポーネント

それでは、まず第一に、コンポーネントとは何ですか? React はその構成要素を指すこの用語を普及させたと思います:

React を使用すると、マークアップ、CSS、JavaScript をカスタムの「コンポーネント」に結合し、アプリで再利用可能な UI 要素を作成できます。
React ドキュメント - 最初のコンポーネント

再利用可能な UI 要素の概念は当時新しいものではありませんでしたが (CSS では、OOCSS、SMACSS、BEM などの技術がすでにありました)、主な違いは、マークアップ、スタイル、および交流。 React コンポーネント (および後続のすべての UI ライブラリ) を使用すると、コンポーネントの境界内の 1 つのファイルにすべてを同じ場所に配置することができます。

Facebook の最新 CSS ライブラリ Stylex を使用すると、次のように書くことができます。

import * as stylex from "@stylexjs/stylex";
import { useState } from "react";

// styles
const styles = stylex.create({
    base: {
        fontSize: 16,
        lineHeight: 1.5,
        color: "#000",
    },
});

export function Toggle() {
    // interactions
    const [toggle, setToggle] = useState(false);
    const onClick = () => setToggle((t) => !t);

    // markup
    return (
        <button {...stylex.props(styles.base)} type="button" onClick={onClick}>
            {toggle}
        </button>
    );
}

オブジェクト記法で CSS を書くのが好きかどうかは別ですが (私はそうではありません)、このレベルのコロケーションはコンポーネントベースのプロジェクトをより保守しやすくする良い方法であることがよくあります。すべてが手の届くところにあり、明示的にバインドされています。

Svelte のようなライブラリでは、コロケーションはさらに明確になります (そしてコードはより簡潔になります)。

<script>
    let toggle = $state(false)
    const onclick = () => toggle = !toggle
</script>

<button type='button' {onclick}>
    {toggle}
</button>

<style>
button {
    font-size: 16px;
    line-height: 1.5;
    color: #000;
}
</style> 

時間が経つにつれて、このパターンは非常に注目を集め、すべてがコンポーネントにカプセル化されるまでになりました。おそらく次のようなページ コンポーネントに遭遇したことがあるでしょう:

export function Page() {
    return (
        <Layout>
            <Header nav={<Nav />} />
            <Body>
                <Stack spacing={2}>
                    <Item>Item 1</Item>
                    <Item>Item 2</Item>
                    <Item>Item 3</Item>
                </Stack>
            </Body>
            <Footer />
        </Layout>
    );
}

1 つのコロケーション

上記のコードはきれいで一貫性があります。ページを記述するためにコンポーネント インターフェイスを使用します。

それでは、スタックの可能な実装を見てみましょう。このコンポーネントは通常、すべての直接の子要素が垂直方向に積み重ねられ、均等な間隔で配置されるようにするためのラッパーです。

import * as stylex from "@stylexjs/stylex";
import type { PropsWithChildren } from "react";

const styles = stylex.create({
    root: {
        display: "flex",
        flexDirection: "column",
    },
    spacing: (value) => ({
        rowGap: value * 16,
    }),
});

export function Stack({
    spacing = 0,
    children,
}: PropsWithChildren<{ spacing?: number }>) {
    return (
        <div {...stylex.props(styles.root, styles.spacing(spacing))}>
            {children}
        </div>
    );
}

スタイルとコンポーネントのルート要素のみを定義します。

この場合、HTML は CSS クラス参照を保持するためにのみ使用され、対話性やビジネスは存在しないため、同じ場所に配置しているのはスタイル ブロックだけである とも言えます。ロジック。

柔軟性の(回避可能な)コスト

それでは、ルート要素をセクションとしてレンダリングして、いくつかの属性を追加できるようにしたい場合はどうすればよいでしょうか?多態性コンポーネントの領域に入る必要があります。 React と TypeScript では、これは次のような結果になる可能性があります:

import * as stylex from "@stylexjs/stylex";
import { useState } from "react";

// styles
const styles = stylex.create({
    base: {
        fontSize: 16,
        lineHeight: 1.5,
        color: "#000",
    },
});

export function Toggle() {
    // interactions
    const [toggle, setToggle] = useState(false);
    const onClick = () => setToggle((t) => !t);

    // markup
    return (
        <button {...stylex.props(styles.base)} type="button" onClick={onClick}>
            {toggle}
        </button>
    );
}

私の意見では、これは一見しただけではあまり読みにくいです。覚えておいてください: 3 つの CSS 宣言を含む要素をレンダリングしているだけです。

基本に戻る

少し前、私は Angular でペット プロジェクトに取り組んでいました。コンポーネントで考えることに慣れていたので、私はスタックを作成するために彼らに連絡を取りました。 Angular では、ポリモーフィック コンポーネントの作成はさらに複雑であることがわかりました。

私は自分の実装設計に疑問を抱き始めましたが、その後、ひらめきました。ソリューションはずっと目の前にあったのに、なぜ複雑な実装に時間とコード行を費やす必要があるのでしょうか?

<script>
    let toggle = $state(false)
    const onclick = () => toggle = !toggle
</script>

<button type='button' {onclick}>
    {toggle}
</button>

<style>
button {
    font-size: 16px;
    line-height: 1.5;
    color: #000;
}
</style> 

実際、これは Stack の最低限のネイティブ実装です。 CSS をレイアウトにロードすると、コード内ですぐに使用できます:

export function Page() {
    return (
        <Layout>
            <Header nav={<Nav />} />
            <Body>
                <Stack spacing={2}>
                    <Item>Item 1</Item>
                    <Item>Item 2</Item>
                    <Item>Item 3</Item>
                </Stack>
            </Body>
            <Footer />
        </Layout>
    );
}

JavaScript フレームワークにタイプ セーフティを追加する

CSS のみのソリューションでは、入力も IDE オートコンプリートも提供しません。

また、間隔バリアントを使用していない場合、間隔プロパティの代わりにクラスとスタイル属性の両方を記述するのは冗長すぎると感じるかもしれません。 React を使用していると仮定すると、JSX を活用してユーティリティ関数を作成できます。

import * as stylex from "@stylexjs/stylex";
import type { PropsWithChildren } from "react";

const styles = stylex.create({
    root: {
        display: "flex",
        flexDirection: "column",
    },
    spacing: (value) => ({
        rowGap: value * 16,
    }),
});

export function Stack({
    spacing = 0,
    children,
}: PropsWithChildren<{ spacing?: number }>) {
    return (
        <div {...stylex.props(styles.root, styles.spacing(spacing))}>
            {children}
        </div>
    );
}

React TypeScript では不明な CSS プロパティが許可されていないことに注意してください。簡潔にするために型アサーションを使用しましたが、より堅牢なソリューションを選択する必要があります。

バリアントを使用している場合は、ユーティリティ関数を変更して、PandaCSS パターンと同様の開発者エクスペリエンスを提供できます。

import * as stylex from "@stylexjs/stylex";

type PolymorphicComponentProps<T extends React.ElementType> = {
    as?: T;
    children?: React.ReactNode;
    spacing?: number;
} & React.ComponentPropsWithoutRef<T>;

const styles = stylex.create({
    root: {
        display: "flex",
        flexDirection: "column",
    },
    spacing: (value) => ({
        rowGap: value * 16,
    }),
});

export function Stack<T extends React.ElementType = "div">({
    as,
    spacing = 1,
    children,
    ...props
}: PolymorphicComponentProps<T>) {
    const Component = as || "div";
    return (
        <Component
            {...props}
            {...stylex.props(styles.root, styles.spacing(spacing))}
        >
            {children}
        </Component>
    );
}

コードの重複とハードコードされた値を防止する

最後の例で、CSS とユーティリティ ファイルの両方に期待される間隔の値をハードコーディングしたことに気付いた方もいるかもしれません。値が削除または追加された場合、2 つのファイルの同期を維持する必要があるため、これが問題になる可能性があります。

ライブラリを構築している場合、自動化されたビジュアル回帰テストでこの種の問題が検出される可能性があります。とにかく、それでも気になる場合は、CSS モジュールに手を伸ばし、typed-css-modules を使用するか、サポートされていない値に対してランタイム エラーをスローすることが解決策になるかもしれません:

<div>





<pre class="brush:php;toolbar:false">.stack {
  --s: 0;
    display: flex;
    flex-direction: column;
    row-gap: calc(var(--s) * 16px);
}
export function Page() {
    return (
        <Layout>
            <Header nav={<Nav />} />
            <Body>
                <div className="stack">



<p>Let's see the main advantages of this approach:</p>

<ul>
<li>reusability</li>
<li>reduced complexity</li>
<li>smaller JavaScript bundle and less overhead</li>
<li><strong>interoperability</strong></li>
</ul>

<p>The last point is easy to overlook: Not every project uses React, and if you’re including the stack layout pattern in a Design System or a redistributable UI library, developers could use it in projects using different UI frameworks or a server-side language like PHP or Ruby.</p>

<h2>
  
  
  Nice features and improvements
</h2>

<p>From this base, you can iterate to add more features and improve the developer experience. While some of the following examples target React specifically, they can be easily adapted to other frameworks.</p>

<h3>
  
  
  Control spacing
</h3>

<p>If you're developing a component library you definitely want to define a set of pre-defined spacing variants to make space more consistent. This approach also eliminates the need to explicitly write the style attribute:<br>
</p>

<pre class="brush:php;toolbar:false">.stack {
  --s: 0;
  display: flex;
  flex-direction: column;
  row-gap: calc(var(--s) * 16px);

  &.s\:1 { --s: 1 }
  &.s\:2 { --s: 2 }
  &.s\:4 { --s: 4 }
  &.s\:6 { --s: 6 }
}

/** Usage:
<div>



<p>For a bolder approach to spacing, see Complementary Space by Donnie D'Amato.</p>

<h3>
  
  
  Add better scoping
</h3>

<p>Scoping, in this case, refers to techniques to prevent conflicts with other styles using the same selector. I’d argue that scoping issues affects a pretty small number of projects, but if you are really concerned about it, you could:</p>

<ol>
<li>Use something as simple as CSS Modules, which is well supported in all major bundlers and frontend frameworks.</li>
<li>Use cascade layers resets to prevent external stylesheets from modifying your styles (this is an interesting technique).</li>
<li>Define a specific namespace like .my-app-... for your classes.</li>
</ol>

<p>Here is the result with CSS Modules:<br>
</p>

<pre class="brush:php;toolbar:false">.stack {
  --s: 0;
  display: flex;
  flex-direction: column;
  row-gap: calc(var(--s) * 16px);

  &.s1 { --s: 1 }
  &.s2 { --s: 2 }
  &.s4 { --s: 4 }
  &.s6 { --s: 6 }
}

/** Usage
import * from './styles/stack.module.css'

<div className={`${styles.stack} ${styles.s2}`}>
    // ...
</div>  
*/

代替案

それでもポリモーフィックコンポーネントの方が良いと思う、プレーンな HTML を扱うことができない、または CSS を別のファイルに書きたくない (理由はわかりませんが) 場合、私の次の提案は次のとおりです。 PandaCSS を見てカスタム パターンを作成するか、vanilla-extract などの他のオプションを検討してください。私の意見では、これらのツールは過剰に設計された CSS メタ言語ですが、それでもポリモーフィック コンポーネントよりは優れています。

検討する価値のあるもう 1 つの代替案は、Tailwind CSS です。これには、言語とフレームワーク間で相互運用できるという利点があります

Tailwind によって定義されたデフォルトの間隔スケールを使用して、次のようなスタック プラグインを作成できます。

import * as stylex from "@stylexjs/stylex";
import { useState } from "react";

// styles
const styles = stylex.create({
    base: {
        fontSize: 16,
        lineHeight: 1.5,
        color: "#000",
    },
});

export function Toggle() {
    // interactions
    const [toggle, setToggle] = useState(false);
    const onClick = () => setToggle((t) => !t);

    // markup
    return (
        <button {...stylex.props(styles.base)} type="button" onClick={onClick}>
            {toggle}
        </button>
    );
}

余談ですが、実際のコンポーネントを作成しない場合でも、Tailwind が複雑な CSS ルールセットを記述するために matchComponents のコンポーネント メンタル モデルを使用していることは興味深いです。この概念がどれほど浸透しているかを示すもう 1 つの例でしょうか?

テイクアウト

コンポーネント炎の事例は、その技術的な側面を超えて、私たちのメンタルモデルと習慣を調べ、疑問を抱くために立ち止まることの重要性を示しています。ソフトウェア開発の多くのパターンと同様、コンポーネントは実際の問題に対する解決策として登場しましたが、このパターンをデフォルトにし始めると、それが複雑さの隠れた原因となりました。 成分炎は、食事制限によって引き起こされる栄養欠乏症に似ています。問題は、特定の食品にあるのではなく、他のすべてを欠いていることにあります。

以上がすべてにコンポーネントが必要なわけではないの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。