7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

`f(data)` 形式だとオプショナルチェーン(`data?.method()`)が無いので、ユーティリティを用意しよう

Posted at

オプショナルチェーンって、便利ですよね?

publishedAt?.toDateString();

「publishedAt を toDateString() で文字列に変換する。ただし、publishedAt が nullable(undefined / null の可能性あり)である。」

のように、data.method() 形式の、変換するタイプの処理で変換元が nullable のときに、コードを短く簡潔にできるので便利ですよね?

ただ、JavaScript には、 f(data) を同様にシンプルにするような機能がありません。


以下のコードを見てみましょう。

import type { FC } from "react";

import { pipe } from "remeda";

type Props = {
  slug: string;
  title: string;
  publishedAt: Date | undefined;
};

const dateTimeFormat = new Intl.DateTimeFormat("ja-JP", {
  timeZone: "UTC",
  dateStyle: "full",
  timeStyle: "short",
});

const formatPublishedAt = (d: Date) => `${dateTimeFormat.format(d)}(UTC)`;

export const PostListItem: FC<Props> = ({ title, publishedAt }) => {
  return (
    <article css={{ padding: "1rem", display: "grid", gap: "0.5rem" }}>
      <h3>{title}</h3>
      {pipe(publishedAt, (d) => {
        if (d == null) return <div>投稿日時の指定なし</div>;
        return <div>{`${formatPublishedAt(d)}`}</div>;
      })}
    </article>
  );
};

Intl.DateTimeFormat を使った formatPublishedAt(date) 関数をコンポーネントの外側に抽出して、この関数をコンポーネント内で呼び出して、指定がない時にはフォールバック表示をしています。


個人的な好みとして、JavaScript には式指向的機能が乏しいので、それ補うために Remeda というライブラリの pipe 関数 を使用しています。

import { pipe } from "remeda";

pipe(data, f, g, h) // === h(g(f(data)))

基本的には、この pipe 関数によって《コードの中を上から下に向かってデータが流れる》という流れを作ります。

data について、f, g, h という変換をこの順に施す」 のように、主題優勢言語っぽく、(OOUI 的な意味での)オブジェクト指向的で、個人的に好きです。


null | undefined を扱うときにも、同様の《流れ》を生み出せるようにしようと思って、こんなユーティリティを作ってみました。

/**
 * null | undefined についてのガードを行わない関数を受け取って、
 * 
 * null | undefined のときに undefined を返すガード付きの関数に変換する。
 */
export const safeNullable =
  <A, B>(fn: (a: A) => B) =>
  (value: A | undefined | null): B | undefined => {
    if (value == null) {
      return undefined;
    }
    return fn(value);
  };

これと pipe を組み合わせると、

  • publishedAt について、
  • これを formatPublishedAt で変換する
    • null | undefined のときは変換せずスルー
  • 最終的に div に挟む、
    • null | undefined のときはフォールバック表示

のような流れが出来ますね?

import type { FC } from "react";

import { pipe } from "remeda";
import { safeNullable } from "../_utils/safe-nullable";

type Props = {
  slug: string;
  title: string;
  publishedAt: Date | undefined;
};

const dateTimeFormat = new Intl.DateTimeFormat("ja-JP", {
  timeZone: "UTC",
  dateStyle: "full",
  timeStyle: "short",
});

const formatPublishedAt = (d: Date) => `${dateTimeFormat.format(d)}(UTC)`;

export const PostListItem: FC<Props> = ({ title, publishedAt }) => {
  return (
    <article css={{ padding: "1rem", display: "grid", gap: "0.5rem" }}>
      <h3>{title}</h3>
      {pipe(
        publishedAt, // publishedAt を対象に取って、
        safeNullable(formatPublishedAt), // これに、 formatPublishedAt を通す (nullish なときはスルー)
        // そして、それを div で包む、 nullish なときはフォールバック表示 
        (d) => (d != null ? <div>{d}</div> : <div>投稿日時の指定なし</div>)
      )}
    </article>
  );
};

まとめ

注意点があります。

safeNullable を使うよりも、書き換え前の「一つのif文を使う」ほうがロジックを素直に書ける場合もあります。

実は、今回のようなケースは、「date を div として出力する」という一つの関数またはコンポーネントを作ってしまったほうが理想的です。

なので、「すべての場合について、こっちのほうがキレイに書ける」という正解があると考えて思考停止するのではなく、該当するロジックと真剣に向き合って、ベターな書き方を検討しましょう。

この記事が、その「ロジックを素直に書ける方法」の選択肢を増やす一助になることを願います。

関連記事

7
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?