2000 年代初期,創造了一個新術語Divitis,用來指稱使用許多div 元素編寫網頁程式碼的做法有意義的語義HTML元素
的位置。這是在漸進增強技術框架內提高 HTML 語義意識的努力的一部分。
快轉 20 年 - 我目睹了一種影響網絡開發者的新綜合症,我稱之為組件炎。這是我編的定義:
元件化:為 UI 的各個面向建立元件來取代更簡單、更可重複使用的解決方案的做法。
那麼,首先,什麼是組件?我認為 React 普及了這個術語來指稱它的構建塊:
React 可讓您將標記、CSS 和 JavaScript 組合到自訂「元件」中,應用程式的可重複使用 UI 元素。
— React 文件 - 您的第一個元件
雖然可重複使用 UI 元素的概念在當時並不新鮮(在 CSS 中,我們已經有了 OOCSS、SMACSS 和 BEM 等技術),但關鍵的區別在於它對標記、樣式和元素位置的原始方法。相互作用。使用 React 元件(以及所有後續 UI 庫),可以將所有內容共同定位在元件邊界內的單一檔案中。
因此,使用 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> ); }
上面的程式碼看起來乾淨且一致:我們使用元件介面來描述頁面。
那麼,讓我們來看看 Stack 的可能實作。該組件通常是一個包裝器,以確保所有直接子元素垂直堆疊且均勻分佈:
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> ); }
純 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 和實用程式檔案中硬編碼了間距的預期值。如果刪除或新增某個值,這可能會成為一個問題,因為我們必須保持兩個檔案同步。
如果您正在建立一個庫,自動化視覺回歸測試可能會發現此類問題。無論如何,如果它仍然困擾您,解決方案可能是使用 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 元語言,但仍然比多態元件更好。
另一個值得考慮的替代方案是 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 使用 matchComponents 中的組件心智模型來描述複雜的 CSS 規則集,即使它沒有創建任何 真實 組件。也許另一個例子可以說明這個概念是多麼普遍?
成分炎的案例,除了技術方面之外,還表明了停下來檢查和質疑我們的心理模式和習慣的重要性。與軟體開發中的許多模式一樣,元件的出現是為了解決實際問題,但是當我們開始預設這種模式時,它就成為了複雜性的無聲來源。 成分炎類似於限制飲食引起的營養缺乏:問題不在於任何單一食物,而在於錯過了其他一切。
以上是並非所有東西都需要組件的詳細內容。更多資訊請關注PHP中文網其他相關文章!