Reactチームは先頃、React HooksをフィーチャーしたReact 16.8をリリースした。Hooksは独立的に再利用、構成、テスト可能な機能構文で、複雑なロジック(状態、効果など)をカプセル化する機能を持つ。React 16.8に同梱されている定義済みのHooksを組み合わせることで、独自のHooksを定義することも可能だ。HooksベースのReactコンポーネントを使用することによって、開発者は、複雑なReactコンポーネントツリーをコンパクトに、理解しやすい形で構築できる。
React HooksはFacebookでは広く使用されてるが、この機能には注意書きが添えられており、残された問題が一部開発者による議論の的になることがある。純粋に機能的なアプローチを支持する別コミュニティの代替案には、しかしながら現時点において、React Hooksの重要なメリットである次期ReactリリースでのConcurrent Modeの実現が含まれていない。
Reactアプリケーションを構成するReactコンポーネントの大部分は、エフェクトを実現し、ローカルおよびグローバルな状態とのインタラクションを行っている。異なるコンポーネントが、同じエフェクトフルな計算を実行することも少なくない。React Hooksは、これらエフェクトフルな計算を機能構文にパッケージすることにより、Reactアプリケーションのフレーム内での再利用を可能にする。ドキュメントには、チャットアプリケーション上で友人がオンラインかオフラインかを示す、FriendStatus
コンポーネントの例が紹介されている。
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
FriendStatus
は、事前定義されたuseState
とuseEffect
を使用して、指定された動作をJavaScript関数として実装している。 useState
は、生成するローカル状態にアクセスするためのsetterおよびgetter APIを公開し、 useEffect
は、FriendStatus
の各レンダリングに対してエフェクトを実行する。エフェクトは、そのエフェクトを実行する関数を通じて指定され、関連するリソースを必要に応じて初期化すると同時に、不要になったリソースを解放するためのクリーンアップ関数を返す。
Reactコンポーネントツリーでは、特定の動作は単一の<FriendStatus>
ノードとして参照される。一般的なクラスベースの振る舞い実装では、高次コンポーネントを経由して状態や効果ロジックを再利用した場合、その高次コンポーネントがコンポーネントツリーを変更する可能性があるため、可読性に悪影響を及ぼす可能性がある。一方で、状態やエフェクトロジックを再利用しなければ、同じコーディングの繰り返しに分類されるバグになる可能性が高い上に、コードサイズが肥大する可能性や、それに起因するユーザーエクスペリエンス低下につながる。
React 16.8には、このようなエフェクトの懸念に対応するため、10の定義済みフックが付属している。これらのフックはReactと密接に統合されており、React的なコンテキストやランタイム以外では意味を持たない。
事前定義済みのReactフックを開発者が組み合わせて、その呼び出しを実装したファンクションであるCustom Hookを構成することが可能である。Reactのドキュメントには、現在選択中の友だちがオンラインかどうかを表示する、ChatRecipientPicker
という、メッセージ受信者ピッカが例として挙げられている。
import React, { useState, useEffect } from 'react';
const friendList = [
{ id: 1, name: 'Phoebe' },
{ id: 2, name: 'Rachel' },
{ id: 3, name: 'Ross' },
];
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
function ChatRecipientPicker() {
const [recipientID, setRecipientID] = useState(1);
const isRecipientOnline = useFriendStatus(recipientID);
return (
<>
<Circle color={isRecipientOnline ? 'green' : 'red'} />
<select
value={recipientID}
onChange={e => setRecipientID(Number(e.target.value))}
>
{friendList.map(friend => (
<option key={friend.id} value={friend.id}>
{friend.name}
</option>
))}
</select>
</>
);
}
ChatRecipientPicker
Reactコンポーネントは、useState
フックとuseFriendStatus
カスタムフックを再利用する。上記の例では、見かけ上はフックの自然なコンポジションであるという、React Hooksの重要な利点を活用している。受信者IDをキャプチャするローカル状態は、パラメータとしてuseFriendStatus
に渡すことが可能であるため、受信者が変更されたタイミングとは独立的に、選択された受診者がオンラインかどうかを計算するという、期待に沿った動作を提供することができる。
React Hooksコンポジションは、構文上は通常のJavaScript関数コンポジションと似ているようにも見えるが、非直感的な意味が異なっている。さらにHooksは動作の都合上、次のようなルールに従って呼び出すことになっている。
- 最上位レベルでのみで呼び出す: ループ、条件、ネストされた関数内で呼び出すことはできない。
- React関数コンポーネントからのみ呼び出す
Hooksの構成動作を管理する規則は、専用のeslintプラグインによって強制することが可能ではあるが、一部の開発者にとっては困難であるため、混乱の元になりかねない。ある開発者は、事情を次のように説明している。
私は特製のライブラリを使用しているので、有効なJavaScriptを有効でないというようなリンタを使う必要はありません。私の意見(に過ぎません)では、これは、プログラミングに関するいくつかの基本的原則に大きく反しています。
他の開発者の中には、Hookを積極的に受け入れた上で、独自の技法や推奨プラクティスを開発しているものもいる。
考えられる解決策として、Reactは、コンポジションの動作をReact Hooksの実装詳細にリンクするための詳細な説明を公開した。しかしながら、セマンティクスを理解してメンタルモデルを構築するために、実装の詳細を把握しなければならない点は、開発者による早期かつ広範な受け入れに対するハードルになる可能性がある。
React Hooksは開発開始から日が浅く、ReactレンダリングパイプラインへのHooksの統合に関連するバグや不一致の存在が確認されているため、陥りやすい落とし穴とその対抗策が、Reactチームによって活発に進められている。
Reactチームでは、既存のクラスベースのコードベースをHooksで書き換えるのではなく、段階的に導入する方法を推奨している。ドキュメントのコメントによると、
今すぐHooksを学ぶ必要はありません。 Hooksに重大な変更はありませんし、Reactからクラスを削除する予定もありません。(…)既存のアプリケーションを、Hooksを使うように、一気に書き直す方法はお勧めしません。そうではなく、新しいコンポーネントのいくつかでHooksを使ってみて、感想を私たちに知らせてほしいのです。Hooksを使用するコードは、クラスを使用する既存のコードと並行して機能します。
Hooksよりも特性的に優れた提案、あるいは純粋な関数アプローチを採用したHooksの代案が、一部のReact開発者によって現在も模索されている。しかし現時点では、いずれの案も、React Hookの重要なメリットである、次期リリースでのConcurrent Renderingの実現を達成できていない。ReduxとCreate React Appの著者のひとりであるDan Abramov氏が詳しく説明している。
レンダリングした値をクロージャで捉えて、それらの値を永続的に見るようにしたいのです。現在値という概念が事実上存在しないコンカレントモードでは、これは本当に重要です。Hooksのデザインでは、現在の状態を切り替えるのではなく、衝突しない多くの状態が同時にあるものとして、コンポーネントをモデル化しています(これはクラスがうまくモデル化できるものです)。このような詳細を考える必要はまったくありませんが、これが設計上のモチベーションを与えているのです。