utils —— ユーティリティ関数
src/utils/ は最も下層の汎用ユーティリティです:core すらこれらに依存しません(core が使うのは core/shared/ のより専門的なアルゴリズム群です)。これらは各 UI ライブラリのコンポーネント実装と利用側にサービスを提供します。
インポート方法
Section titled “インポート方法”import { createNamespace, deepMerge, debounce, generateId } from '@lh-ui/share/utils'| 関数 | シグネチャ | 役割 |
|---|---|---|
generateId |
(prefix = 'lh') => string |
ユニーク ID を生成(${prefix}_${Date.now()}_${counter}) |
generateRandomId |
() => string |
ランダム ID を生成(7 文字の base36) |
isEmpty |
(val: unknown) => boolean |
空値かどうか(null / undefined / '' のみ) |
isString |
(val: unknown) => val is string |
型ガード:文字列 |
isNumber |
(val: unknown) => val is number |
型ガード:数値(NaN は false) |
isPlainObject |
(val: unknown) => val is Record<string, unknown> |
型ガード:プレーンオブジェクト |
debounce |
<T>(fn: T, delay = 300) => LhDebounced<T> |
デバウンス(cancel / flush 付き) |
throttle |
<T>(fn: T, delay = 300) => LhThrottled<T> |
スロットル(先頭で即実行 + 末尾で遅延実行) |
deepMerge |
<T>(target: T, ...sources: Partial<T>[]) => T |
オブジェクトのディープマージ |
getScrollbarWidth |
() => number |
スクロールバーの幅を取得(結果はキャッシュ) |
toggleClass |
(el, className, force?) => void |
CSS class の追加 / 削除 |
getEventValue |
(e: Event) => string |
イベントから対象の値を取得 |
サブモジュールのエクスポート(@lh-ui/share/utils の末尾で re-export):
| 関数 | シグネチャ | 役割 |
|---|---|---|
createNamespace |
(block: string) => LhNamespace |
BEM 名前空間ユーティリティ |
toCssVarName |
(key: string) => string |
CSS 変数名を生成(すでに --lh- プレフィックスが付いたキーはそのまま返す、べき等) |
cssVars |
(vars: Record<string, string>) => Record<string, string> |
themeVars のキーと値のペア → :style にバインドできる CSS 変数オブジェクト |
stripCssVarName |
(key: string) => string |
--lh- プレフィックスを取り除き、themeVars prop のキーに戻す |
createNamespace —— BEM 名前空間
Section titled “createNamespace —— BEM 名前空間”const ns = createNamespace('button')
ns.b() // 'lh-button'ns.e('text') // 'lh-button__text'ns.m('primary') // 'lh-button--primary'ns.em('text', 'disabled') // 'lh-button__text--disabled'ns.is('loading', true) // 'is-loading'ns.is('loading', false) // ''(空文字、フレームワークは無視)すべての lh-ui コンポーネントのクラス名はこれを経由して生成され、BEM 構造の一貫性と lh- プレフィックスの漏れを防ぎます。is() の状態クラスは名前空間プレフィックスを帯びません(lh-button--is-loading ではなく is-loading)——これがファミリーの慣例です。
型:
export interface LhNamespace { b: () => string // Block e: (element: string) => string // Element m: (modifier: string) => string // Modifier em: (element: string, modifier: string) => string // Element + Modifier is: (name: string, state?: boolean | string) => string // 状態クラス}debounce / throttle
Section titled “debounce / throttle”debounce —— 沈黙期間中は最後の 1 つだけ実行
Section titled “debounce —— 沈黙期間中は最後の 1 つだけ実行”const debounced = debounce((q: string) => search(q), 300)input.addEventListener('input', (e) => debounced(e.target.value))
debounced.cancel() // アンマウント時に未実行の保留呼び出しを片付けるdebounced.flush() // 保留されている 1 回を即座に実行(保留がない場合は何もしない)cancel / flush はただの飾りではありません:コンポーネントのアンマウント時にタイマーを片付けないと、すでに破棄されたコンテキストを呼んでしまいます;「今すぐ検索」ボタンは flush() でまだ発火していないクエリを即座に走らせます。
throttle —— 先頭で即実行 + 末尾で遅延実行
Section titled “throttle —— 先頭で即実行 + 末尾で遅延実行”const throttled = throttle(onScroll, 100)window.addEventListener('scroll', throttled)時間窓の先頭で即座に実行(先頭エッジ)し、窓の中の最後の呼び出しは窓の終わりに遅延実行(末尾エッジ)します。素朴なスロットルは高速スクロールの最後の 1 フレームを落としてしまいます——スクロールが止まったその瞬間こそ最も更新が必要なときです(sticky / affix の位置判定)。
throttle には cancel しかありません(末尾タイマーのクリア + lastTime のリセット)。flush はありません(末尾エッジは意味的に「後で実行」だからです)。
型:
export interface LhDebounced<T> { (...args: Parameters<T>): void cancel(): void flush(): void}
export interface LhThrottled<T> { (...args: Parameters<T>): void cancel(): void}cssVars 3 点セット
Section titled “cssVars 3 点セット”ConfigProvider の themeVars prop は省略記法のキー('primary-color')を使い、コンポーネントが消費するのは CSS 変数(--lh-primary-color)です。この 3 つの関数が両方向の変換をします:
cssVars({ 'primary-color': '#1677ff' })// { '--lh-primary-color': '#1677ff' } ← そのまま :style にバインド可能
toCssVarName('primary-color') // '--lh-primary-color'toCssVarName('--lh-primary-color') // '--lh-primary-color'(べき等)
stripCssVarName('--lh-primary-color') // 'primary-color'toCssVarName のべき等性が重要です:一部のパスで既にプレフィックスが付いているキーが二重に補われて --lh---lh-primary-color になるのを防ぎます——そのようなキーは(CSS 変数が存在しないため)黙って無効になり、テーマ切り替えが「反応しない」ように見えます。
deepMerge
Section titled “deepMerge”deepMerge({ a: 1, nested: { x: 1 } }, { nested: { y: 2 } })// { a: 1, nested: { x: 1, y: 2 } }- 両側がともにプレーンオブジェクトのときだけ再帰します。
sourceVal !== undefinedのときだけ上書きします(明示的にundefinedを渡しても上書きしない)。- target を破壊的に変更して返す——ここが注意点で、元のオブジェクトを汚したくない場合は先に
{ ...target }で 1 層シャローコピーしてください。
getScrollbarWidth
Section titled “getScrollbarWidth”getScrollbarWidth() // 例:15(Windows)/ 0(macOS でスクロールバーなし)結果はモジュール変数 _scrollbarWidth にキャッシュされます。SSR ガード:typeof document === 'undefined' のときは 0 を返します。
用途:dialog / drawer を開いたときに body のスクロールをロックすると右側のスクロールバー 1 本分の幅が減るため、padding-right で補わないとページの内容が横に揺れます。
コンポーネント内の BEM + class の結合
Section titled “コンポーネント内の BEM + class の結合”const ns = createNamespace('dialog')
const headerClass = computed(() => [ ns.e('header'), ns.is('centered', props.centered), // ユーザーが渡した class ...(typeof attrs.class === 'string' ? attrs.class.split(' ') : []),])ConfigProvider がテーマ変数を注入
Section titled “ConfigProvider がテーマ変数を注入”const styleVars = computed(() => cssVars(props.themeVars))// <div :style="styleVars">スクロール監視のスロットル
Section titled “スクロール監視のスロットル”onMounted(() => { const onScroll = throttle(updateAffixPosition, 100) window.addEventListener('scroll', onScroll, true) onUnmounted(() => { window.removeEventListener('scroll', onScroll, true) onScroll.cancel() })})isEmptyが判するのは 3 つだけ:null/undefined/''。0/false/[]は空ではありません。「空の配列 / 空のオブジェクト」を判したい場合は自分で書いてください。isNumber(NaN)はfalse:意図的に NaN を排除しています。deepMergeは target を破壊的に変更します:最もよくある誤用の源です。generateIdは純関数ではありません:モジュールレベルの自増カウンターで、同一プロセス内で複数回呼ぶと結果が異なります(SSR / CSR で順序を一致させる必要があります、a11y の generateAriaId の説明を参照)。useLhSizeなどの composables はここにありません:Vue の Composition API に依存するものは各フレームワークの UI 層に属し、share はエクスポートしません。
@lh-ui/share/core/utils → createNamespace( )(填好参数后点运行)Copyright © 2024-2026 FengMo WenXue