跳至內容

動態樣式

pika() 的引數必須是靜態的,但你的 UI 不是。本頁介紹涵蓋執行階段驅動樣式的各種模式。

為什麼會有這個限制

在建置時期,外掛會從你的原始碼擷取每一次 pika() 呼叫,並把它的引數當成一段自足的運算式來求值,過程中不會執行你模組裡的其他程式碼(packages/integration/src/ctx.ts)。任何引用變數、呼叫函式,或根據執行階段狀態分支的內容,都無法用這種方式求值:

ts
// ❌ 這些在建置時期會失敗:呼叫看不到 `color` 或 `isDark`
pika({ color })
pika({ color: isDark ? 'white' : 'black' })

ESLint 規則 no-dynamic-args 會在建置之前先抓出這些問題。下面每個模式背後的關鍵觀念是:樣式的集合必須是靜態的,但你在執行階段要套用哪個樣式完全由你決定pika() 只是回傳一個字串。

模式 1:Variant Map

每個 variant 寫一次 pika() 呼叫,然後在執行階段從中選擇。每一次呼叫都在建置時期編譯;選擇的動作只是單純的物件存取。

ts
const buttonVariants = {
	primary: pika({
		backgroundColor: '#3b82f6',
		color: 'white',
	}),
	danger: pika({
		backgroundColor: '#ef4444',
		color: 'white',
	}),
}

// 在執行階段選擇 variant 只是單純的物件存取:
// 上面每一次 pika() 呼叫在建置時期就已經編譯完成。
export function buttonClass(kind: keyof typeof buttonVariants) {
	return buttonVariants[kind]
}
css
@layer utilities {
  .pk-a {
    background-color: #3b82f6;
  }
  .pk-b {
    color: white;
  }
  .pk-c {
    background-color: #ef4444;
  }
}

注意在輸出裡,共用的宣告(color: white)會在各個 variant 之間去除重複,合併成同一個原子 class。

布林狀態的運作方式相同:

ts
const tabClass = pika({ padding: '0.5rem 1rem' })
const activeTabClass = pika({ fontWeight: '700', color: '#3b82f6' })

// 在執行階段組合建置時期產生的字串
const className = isActive ? `${tabClass} ${activeTabClass}` : tabClass

警告

當兩個 variant 在同一個元素上設定了重疊的屬性時,哪一個會勝出是由樣式表決定,而不是由 class 在標記(markup)中出現的順序決定。請讓 variant map 在每個屬性上互斥,或像上面那樣把基礎樣式與 variant 樣式拆開。見 PikaCSS 如何產生 CSS

模式 2:用 CSS 變數處理真正的執行階段值

當數值本身只存在於執行階段時(滑桿位置、計算出來的顏色、使用者內容),可在樣式定義裡引用一個 CSS 自訂屬性,並透過行內 style 屬性來設定該屬性:

ts
const progressBarClass = pika({
	width: 'var(--progress, 0%)',
	height: '0.5rem',
	backgroundColor: '#3b82f6',
	transition: 'width 0.2s ease',
})

export { progressBarClass }
css
@layer utilities {
  .pk-a {
    width: var(--progress, 0%);
  }
  .pk-b {
    height: 0.5rem;
  }
  .pk-c {
    background-color: #3b82f6;
  }
  .pk-d {
    transition: width 0.2s ease;
  }
}

接著在執行階段餵入這個變數;原子 class 永遠不會改變:

vue
<div :class="progressBarClass" :style="{ '--progress': `${percent}%` }" />
tsx
<div className={progressBarClass} style={{ '--progress': `${percent}%` }} />

模式 3:把 Shortcut 當成配方

對於帶有具名 variant 的可重複使用多屬性樣式,可在引擎設定裡定義 shortcut。shortcut 可以互相組合(一個 variant 可以包含基礎 shortcut),而使用時仍然維持為靜態字串:

ts
// pika.config.ts
import { defineEngineConfig } from '@pikacss/core'

export default defineEngineConfig({
	shortcuts: {
		definitions: [
			['btn', {
				display: 'inline-flex',
				alignItems: 'center',
				padding: '0.5rem 1rem',
				borderRadius: '0.5rem',
				border: 'none',
				cursor: 'pointer',
			}],
			['btn-primary', ['btn', { backgroundColor: '#3b82f6', color: 'white' }]],
			['btn-danger', ['btn', { backgroundColor: '#ef4444', color: 'white' }]],
		],
	},
})
ts
const primaryButtonClass = pika('btn-primary')
const dangerButtonClass = pika('btn-danger')

export { dangerButtonClass, primaryButtonClass }
css
@layer utilities {
  .pk-a {
    display: inline-flex;
  }
  .pk-b {
    align-items: center;
  }
  .pk-c {
    padding: 0.5rem 1rem;
  }
  .pk-d {
    border-radius: 0.5rem;
  }
  .pk-e {
    border: none;
  }
  .pk-f {
    cursor: pointer;
  }
  .pk-g {
    background-color: #3b82f6;
  }
  .pk-h {
    color: white;
  }
  .pk-i {
    background-color: #ef4444;
  }
}

與模式 1 結合,即可在執行階段選擇一個 shortcut:

ts
const buttonVariants = {
	primary: pika('btn-primary'),
	danger: pika('btn-danger'),
}

動態的 shortcut 規則(以正規表達式為基礎,例如 @pikacss/plugin-icons 提供的圖示 shortcut)仍然要求比對到的字串本身要靜態地出現在某個 pika() 呼叫裡。

選擇一個模式

情境模式
少數幾個已知的 variant(尺寸、用途、作用中狀態)Variant map
在執行階段計算出來的值(位置、百分比、使用者顏色)CSS 變數 + 行內樣式
跨元件共用的可重複使用配方Shortcut

下一步