オプショナルチェーンって、便利ですよね?
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 として出力する」という一つの関数またはコンポーネントを作ってしまったほうが理想的です。
なので、「すべての場合について、こっちのほうがキレイに書ける」という正解があると考えて思考停止するのではなく、該当するロジックと真剣に向き合って、ベターな書き方を検討しましょう。
この記事が、その「ロジックを素直に書ける方法」の選択肢を増やす一助になることを願います。
関連記事