React 動態 className 的最佳實踐

前言

React根據條件判斷所產生的動態className也是另外一個可能會讓代碼變混亂的來源。

找些資料,試著寫個 best practice,以後有新的發現也會持續更新。

falsy value

在不套用 React 生態一堆稀奇古怪的 css solution 情形下,我們把falsy value 帶到 className 裡面,看一下 react 會怎麼處理

export default function App() {
    return (
        <div
            className={`
                active
                ${'' && 'active1'}
                ${false && 'active2'}
                ${undefined && 'active3'}
                ${null && 'active4'}
                ${0 && 'active5'}
                ${NaN && 'active6'}
                ${false ? 'active7' : ''}
            `}>
            <div>hello world</div>
        </div>
    )ˋ
}

結果顯示

className

  • 使用邏輯操作符&&,除了空字串,其他 false value 都被打印出來了,這肯定不是我們要的
  • 使用三元操作符,讓判斷式為false時,回傳空字串,就能符合我們的預期。
    • 在沒有任何額外加工的情況下,上面三元操作符的寫法,可讀性還可以,但是代碼有點落落長。

package 解法

列了三個解法:

merge-class-names檔案最小,但是他沒有我想要的寫法

clxsclassnames支援的各種寫法幾乎一樣,但是 clsx 檔案比較小,專案比較新,雖然星星數差了 3 倍,不過 clsx 有 6000 多顆星星,也不少就是了

土炮解法

我最理想的寫法是可以寫固定的字串,跟動態的用 object 去判斷

示意如下:

classNames('foo', { bar: true }) // => 'foo bar'

寫了一個 utillity 叫 classNames 的 function 去達到我要的要求。

export default function App() {
    return (
        <div
            className={classNames('active', { active2: true, active3: NaN }, 'active4', {
                active5: undefined,
                active6: true
            })}>
            <div>hello world</div>
        </div>
    )
}

function classNames() {
    return Object.keys(arguments)
        .map((key) => {
            const item = arguments[key]
            if (typeof item === 'object') {
                return Object.keys(item).filter((k) => item[k])
            }

            return item
        })
        .flat()
        .join(' ')
}

結果 result

我們來看一下 classNames 是怎麼實現的

  • 輸入的參數只有兩種型別,stringobject
  • 透過arguments變量(iterable object),用Object.keys對其作遍歷,因為有 key,回圈時就能對應到該個 value
  • value 是string時,不額外作為
  • value 是object時(相當於代碼中的 item),一樣對該 item 做遍歷
  • filter過濾,留下值是 true 的
  • 出來的結果會是一個類似這樣的東西,['tag1', ['tag2','tag3'],tag4,['tag5','tag5']]
  • 然後再用 flat(),去把 nesting array 攤平
  • 在用 join()空格做連接,把 array 組合成一個字串,就是我們需要的啦

套用 css module

套用一下常見的 css module,看一下代碼會變成怎樣

import { active, active2, active3, active4, active5, active6 } from './App.module.css'
export default function App() {
    return (
        <div
            className={classNames(active, { [active2]: true, [active3]: NaN }, active4, {
                [active5]: undefined,
                [active6]: true
            })}>
            <div>hello world</div>
        </div>
    )
}

因為原本是 string 的部分,都被取代成 variable 了。

在 object 中,key 用 variable 表示的話,要多個[],就變成[active2]: true