それ、もうWeb標準APIでできるんです
みなさんも日々身にしみて感じていらっしゃるとは思いますが、Webの技術の変革の速度は目を見張るものがあります。数ヶ月前の常識が時代遅れになってしまうというネガティブな面もあれば、以前は困難だったものが今ではものすごく簡単に出来てしまうといったポジティブな側面もあります。
本記事で扱うのは後者で、以前はライブラリに頼って実装していたものが、最近ではWebのスタンダードなAPIで出来るようになったという例をいくつかご紹介したいと思います。
ユニークなIDの発行
まずはUUIDの発行です。従来は uuid
や nanoid
をインストールして生成していましたが、 Web Crypto API がどの環境でも使えるようになり、簡単にユニークなIDを生成できるようになりました。
const uuid = crypto.randomUUID(); // => ex) '71b9eb20-39c4-48f1-9680-6f804c79bc6c'
グローバルに crypto
という Crypto
インターフェースのインスタンスが生えており、それを参照して randomUUID()
メソッドを呼び出すことでUUIDが取得できます。引数は受け取らないため uuid
や nanoid
のようにバージョンや桁数をカスタマイズすることはできませんが、シンプルにUUIDを使いたい場合には十分有用です。ただし、この機能は https
のような「安全なコンテキスト」内でしか使えませんので注意しましょう。非セキュアな環境では randomUUID
は undefined
となります。
ユニークなIDの身近な使い道の例としては React の map
処理における key
の生成が挙がります。 map
で回されるアイテムの要素に id
が振られていないなど、 key
として使えそうなものがない場合に活用できます。
export function ListComponent ({ items }: { items: string[] }): JSX.Element {
const itemsWithKeys = useMemo(() => (
items.map((value) => ({ key: crypto.randomUUID(), value }))
), [items]);
return (
<ul>
{itemsWithKeys.map(({ key, value }) => (
<li key={key}>{value}</li>
))}
</ul>
);
}
URLのバリデーション
次は、文字列が正しいURLかどうかを判別するバリデーションです。今までは正規表現を使ったり validator のようなパッケージを活用するなどして実装していたと思います。下のコードは validator パッケージによる例です。
import { isURL } from 'validator';
isURL('https://www.example.com'); // => true
isURL('foobarbaz'); // => false
現在ではパッケージに依存せずに標準の URLコンストラクタ でこれを実装することができます。URLコンストラクタは引数にURL文字列を受け取りますが、文字列が正しいURLの書式でなかった場合に TypeError: Invalid URL
をスローします。
new URL('foobarbaz'); // Uncaught TypeError: Failed to construct 'URL': Invalid URL
つまり例外がスローされた場合は正しいURLではないということなので、その例外を catch
して false
を返せばよいですね。
ちなみに正しいURLだった場合は URLインターフェースのオブジェクトが生成されます。URLインターフェースではプロトコル( protocol
)が参照出来るので、正としたいプロトコルの場合だけ true
を返すようにしましょう。これで ftp://
のようなURL文字列を弾く事ができます。
function isURL (value: string): boolean {
try {
const url = new URL(value);
return ['http:', 'https:'].includes(url.protocol);
} catch (error) {
return false;
}
}
Emailのバリデーション
では、Email文字列のバリデーションはどうでしょうか。残念ながらURLの様に便利なインターフェースは用意されてはいない様なので、別の方法を考えてみました。 HTMLInputElement
の ValidityState を活用してみます。
先に申し上げておきますが、お勧めできる手法ではありません。
HTMLInputElement
すなわち <input />
要素は type
属性で挙動を選択する事ができますが、その中に email
があります。これが指定された場合、 input
要素は入力ごとに値が正しいメールアドレス文字列かどうかを検証するようになります。バリデーションの結果は validity
プロパティで参照することができます。
const $input = document.createElement('input');
$input.type = 'email';
$input.value = 'webmaster@example.com';
console.log($input.validity.valid); // => true
$input.value = 'foobarbaz';
console.log($input.validity.valid); // => false
これを関数にまとめれば isEmail()
の出来上がりという寸法ですが、それだけではバリデーションのたびに input
要素を生成することになり、計算コストが気になります。せっかくなのでReact での使用を前提として、生成した input
要素を使い回せるように hooks
化してみたいと思います。
import { useEffect, useState } from 'react';
export const useIsEmail = (): [((value: string) => boolean) | undefined] => {
const [isEmail, setIsEmail] = useState<(value: string) => boolean>();
useEffect(() => {
const $input = document.createElement('input');
$input.type = 'email';
setIsEmail(() =>
(value: string): boolean => {
$input.value = value;
return $input.validity.valid;
}
);
}, []);
return [isEmail];
};
Reactはレンダリングされてからでないと document
は参照できないので、 useEffect
を使って回避しており想定外にコードが複雑化してしまいました。
このような使い方を想定しています。
const [isEmail] = useIsEmail();
console.log(isEmail?.('info@example.com'));
なぜお勧めできないか
このやり方がお勧めできない理由はいくつかあります。
- 仕様で想定されていない使い方で強引に実装している
document
を参照するため node.js で使えない(フロントエンドとバックエンドで異なるロジックで検証をすることになる)- レンダリングを待つ必要があるため CLS などに悪影響を及ぼす可能性がある
一番重要だと考えるのは 1. で、正規の方法ではないバッドノウハウで実装した場合は予想されない不具合が生じる可能性もあるでしょう。とはいえこういう泥臭い手法もあるのだと知っておくことは無駄にはならないと思います。どこかで役立つ場面があるかもしれません。
Arrayの操作
近年のJavaScriptの進化で最も分かりやすいのは、おそらく Array のメソッド群でしょう。以前は lodash の関数等を活用していたようなものも、今ではその多くが標準のインスタンスメソッドで簡単に再現出来るようになりました。
例えば配列の末尾を取得するための _.last
は Array.prototype.at
で引数に -1
を渡すことで再現できます。
const array = ['foo', 'bar', 'baz'];
_.last(array); // => 'baz'
array.at(-1); // => 'baz'
また例えば、多次元配列を一次元にする _.flatten
_.flattenDeep
は、 Array.prototype.flat
で代替できます。 .flat()
は引数に深さレベルを指定できるので、実質上位互換と言えます。
const array = [1, 2, 3, [4, 5, [6, 7, 8]], 9];
_.flattenDeep(array); // => [1, 2, 3, 4, 5, 6, 7, 8, 9]
array.flat(Infinity); // => [1, 2, 3, 4, 5, 6, 7, 8, 9]
従来は回りくどい手順を踏まなければならなかった処理が1メソッドで済むようになった例もあります。 Array.prototype.with
はインデックスを指定して値を変更し、結果を新しい配列として返します。
// 従来
const array = ['foo', 'bar', 'baz'];
array[1] = 'qux';
console.log(array); // => ['foo', 'qux', 'baz']
// 新しい方法
const array = ['foo', 'bar', 'baz'];
console.log(array.with(1, 'qux')); // => ['foo', 'qux', 'baz']
この例では一行減っただけですが、メソッドチェーンの中で使うことが出来るのは大きなメリットではないでしょうか。
この他にも有用なメソッドは数多くあります。たまにメソッド一覧を眺めたりすると、新しい発見があるかもしれません。
- Array – JavaScript | MDN https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array
最後に
繰り返しになりますが、Webの環境はどんどん変化していきます。ライブラリはリッチになり、生のJavaScriptで出来ることも増えていきます。わたしたちはより良いプロダクトをつくるため、それらを常にキャッチアップしていかなければなりません。
数年後は一体どんな世界になっているのか、楽しみですね。
Gaji-Labo は新規事業やサービス開発に取り組む、事業会社・スタートアップへの支援を行っています。
弊社では、Next.js を用いた Web アプリケーションのフロントエンド開発をリードするフロントエンドエンジニアを募集しています!さまざまなプロダクトやチームに関わりながら、一緒に成長を体験しませんか?
もちろん、一緒にお仕事をしてくださるパートナーさんも随時募集中です。まずはお気軽に声をかけてください!