KDE BLOG

不器用ですから

【JavaScript 小ネタ】JSXなどのテンプレート上でクラス名に入る無駄な空白(スペース)をよしなに削除する方法

ちょっとした小ネタを思いついたので書いておきたいと思います。

JSX上などで、クラス名の出し分けとかによくある下記のような記述をすると、場合によっては無駄な空白(スペース)が開発者ツール上で見えたりして気になったりしないでしょうか?

<a href="#" className={`foo ${isActive ? "is-active" : ""} ${isHidden ? "is-hidden" : ""}`}>test</a>

Chromeの開発者ツールでの見え方

f:id:jinseirestart:20181104163937p:plain

foo の後ろに、無駄な空白が入ってしまっています。

こういう時皆さんはどうしていますか?

自分の場合は今まで、挙動には問題ないのでそのまま無視するか、下記のように空白が入ることを想定した書き方をしていました。

<a href="#" className={`foo${isActive ? " is-active" : ""}${isHidden ? " is-hidden" : ""}`}>test</a>

${...} の前に空けていた空白を削除して詰めれば、無駄な空白は生まれなくなります。
しかし 可読性が低下 してしまうので良い方法とは言えません。
何かスマートに解決できる方法があればいいなーとなんとなく思っていました。

タグ付きテンプレートリテラル で解決

こんな時こそ、以前勉強した タグ付きテンプレートリテラル が生かされる時でした。

kde.hateblo.jp

結論として下記コードで解決できます。

/**
 * 前後の空白を削除
 * @return {String}
 */
const triming = text => text.trim();

/**
 * 空要素ではないか判定
 * @return {Boolean}
 */
const isNotEmpty = text => text !== "";

/**
 * 配列内の各要素をトリミングして、空白のみの要素を除去した配列を取得
 * @return {Array}
 */
const filterMapTrim = ary => ary.map(triming).filter(isNotEmpty);

/**
 * クラス名用に文字列を組み立てる
 * @return {String}
 */
const createStrForClassName = (strings, ...values) => {
  const stringsFiltered = filterMapTrim(strings);
  const valuesFiltered = filterMapTrim(values);
  return [...stringsFiltered, ...valuesFiltered].join(" ");
}

<a href="#" className={createStrForClassName`foo ${isActive ? " is-active" : ""} ${isHidden ? " is-hidden" : ""}`}>test</a>
// 結果
// <a href="#" class="foo">test</a>

テスト

let isActive = true;
let isHidden = true;

console.log(
  // 空白の場合
  createStrForClassName`` === "",
  // 文字列の前後に空白がある場合
  createStrForClassName` foo ` === "foo",
  // 複数文字列のあとに空白がある場合
  createStrForClassName`foo bar ` === "foo bar",

  // 変数の前後を詰めた場合
  createStrForClassName`foo bar${isActive ? "is-active" : ""}${
    isHidden ? "is-hidden" : ""
  }` === "foo bar is-active is-hidden",
  // 返り値の前後に空白をつけた場合
  createStrForClassName`${isActive ? " is-active " : ""} ${
    isHidden ? "is-hidden " : ""
  }` === "is-active is-hidden",
);

isActive = false;
isHidden = true;

console.log(
  // 途中の変数が空白の場合
  createStrForClassName`foo bar ${isActive ? "is-active" : ""} ${
    isHidden ? " is-hidden " : ""
  }` === "foo bar is-hidden"
);

すべて true になったので上手くいっているようです。

注意点

この関数は決して万能というわけではなく、下記の場合は false となります。

const isActive = true;
const isHidden = true;

console.log(
    // 文字列の間に連続して空白が入っている場合
    createStrForClassName`foo    bar ${isActive ? "is-active" : ""} ${
      isHidden ? " is-hidden " : ""
    }` === "foo bar is-active is-hidden"
  );

なぜかというと、タグ関数の仕様が関係してきます。
タグ関数の第一引数 strings にはテンプレートリテラル${} 以外の箇所が配列として入っています。
これを確認してみると、連続した空白がそのまま入っていることが分かります。

const createStrForClassName = (strings, ...values) => {
  console.log(strings); // ["foo    bar ", " ", ""]
  // 略
};

今回の関数では、この配列の要素ごとに String.prototype.trim() を適用しているので、foo bar と前後の空白は削除されますが、 foo bar とはなりません。
参考: String.prototype.trim() - JavaScript | MDN

こういったケースにも正規表現など使えば対応させることはできますが、複雑化する上に、文字列のクラス名くらいなら普通に半角スペースで区切っての入力は問題なくするだろうという性善説の上に乗って、今回は終了したいと思います。

でもやはりなんだかんだめんどくさいですね。。。
費用対効果が薄い気がするので無視が一番ですかね。