メインコンテンツにスキップ
バージョン: v18.0.0

@required ディレクティブ

@required ディレクティブは、Relay クエリ内のフィールドに追加して、実行時に null 値をどのように処理するかを宣言するために使用できます。これは、「このフィールドが null の場合、その親フィールドは無効であり、null であるべき」と述べていると考えることができます。

多くのフィールドが null 許容である GraphQL スキーマを使用する場合、基礎となるデータを使用する前に、各フィールドの潜在的な「null 性」を処理するためにかなりの量の製品コードが必要になります。@required を使用すると、Relay はコンポーネントにデータを返す前に、ある種の null チェックを処理できます。つまり、**`@required` でアノテーションを付けたフィールドは、レスポンスの生成された型では null 非許容になります**。

実行時に `@required` フィールドが null の場合、Relay はその null 性をフィールドの親に「バブルアップ」します。たとえば、次のクエリがあるとします。

query MyQuery {
viewer {
name @required(action: LOG)
age
}
}

name が null の場合、Relay は { viewer: null } を返します。この場合、`@required` は「`viewer` は `name` がなければ役に立たない」と述べていると考えることができます。

アクション

@required ディレクティブには、3 つの可能な値を持つ必須の `action` 引数があります。

NONE (想定内)

このフィールドは、時々 null になることが想定されています。

LOG (回復可能)

この値は null になることは想定されていませんが、null の場合でもコンポーネントは **レンダリングできます**。 `action: LOG` を持つフィールドが null の場合、Relay 環境ロガーは次のようなイベントを受信します。

{
name: 'read.missing_required_field',
owner: string, // MyFragmentOrQueryName
fieldPath: string, // path.to.my.field
};

THROW (回復不可能)

この値は null ではなく、コンポーネントは **それなしではレンダリングできません**。実行時に `action: THROW` を持つフィールドが null の場合、そのフィールドを読み取るコンポーネントは **レンダリング中にスローします**。エラーメッセージには、オーナーとフィールドパスの両方が含まれます。このオプションは、コンポーネントが エラー境界 内に含まれている場合にのみ使用してください。

局所性

フィールドの `@required` ステータスは、**それが指定されているフラグメントに対してローカルです**。これにより、コンポーネントのスコープ外のことを考慮せずにディレクティブを追加/削除できます。

この選択は、一部のコンポーネントは、他のコンポーネントよりも欠落データからうまく回復できる可能性があるという事実を反映しています。たとえば、<RestaurantInfo /> コンポーネントは、レストランの住所が欠落していても、おそらく意味のあるものをレンダリングできますが、<RestaurantLocationMap /> コンポーネントはレンダリングできない可能性があります。

ただし、単一のフラグメント内の同じフィールドに対する `@required` ディレクティブのすべての使用法は、それらの使用法と一致している必要があります。この状況は、インラインフラグメントでフィールドを選択するときに主に発生します。たとえば、次のフラグメントはコンパイルに失敗します。

fragment UserInfo on User {
job {
... on Actor {
certifications
}
... on Lawyer {
certifications @required(action: LOG)
}
}
}

Relay コンパイラは、`フィールドへのすべての参照は、一致する @required 宣言を持っている必要があります。` のようなエラーを返します。これを修正するには、インラインフラグメントで選択された各フィールドに `@required` ディレクティブを設定するか、ディレクティブを完全に削除します。

連鎖

@required ディレクティブを連鎖させて、1 回の null チェックだけで深くネストされたフィールドにアクセスできるようにすることができます。

const user = useFragment(graphql`
fragment MyUser on User {
name @required(action: LOG)
profile_picture @required(action: LOG) {
url @required(action: LOG)
}
}`, key);
if(user == null) {
return null;
}
return <img src={user.profile_picture.url} alt={user.name} />

**注**: フラグメントのトップレベルフィールドで `@required` を使用すると、`useFragment` から返されるオブジェクト自体が null 許容になる場合があります。生成された型はこれを反映します。

@required ディレクティブを連鎖する場合、Relay コンパイラは、意図したよりも深刻なアクションで連鎖を誤って作成しないようにします。次のフラグメントを考えてみましょう。

fragment MyUser on User {
profile_picture @required(action: THROW) {
url @required(action: LOG)
}
}

この例では、`profile_picture` フィールドが null の場合はコンポーネントを THROW し、`url` フィールドが null の場合はエラーを LOG するだけにすることを目的としています。しかし、思い出してください。Relay は null 性を親フィールドに「バブルアップ」します。`url` フィールドが null の場合、`profile_picture` フィールドも null になります。それが発生すると、コンポーネントは THROW します。このようなパターンを実装すると、Relay コンパイラはエラーを返します。

A @required field may not have an `action` less severe than that of its @required parent. This @required directive should probably have `action: LOG` so that it can match its parent

これを修正するには、`profile_picture` を `action: LOG` を使用するように変更するか、`url` フィールドを `action: THROW` を使用するように変更します。

コネクションに関する注意点

現在、`@required` ディレクティブと `@connection` ディレクティブを一緒に使用することには、いくつかの制限があります。`@connection` ディレクティブを使用すると、Relay はコネクションにいくつかの追加フィールドを自動的に挿入しますが、それらのフィールドは `@required` ディレクティブで生成されません。Connection 型のフィールドで `@required` ディレクティブを使用すると、不整合が生じる可能性があります。次の例を考えてみましょう。

fragment FriendsList on User @refetchable(queryName: "FriendsListQuery") {
friends(after: $cursor, first: $count) @connection(key: "FriendsList_friends") {
edges {
node @required(action: LOG) {
job @required(action: LOG) {
title @required(action: LOG)
}
}
}
}
}

`node` フィールドまたはその直接の子フィールドで `@required` を使用すると、Relay コンパイラは「フィールドへのすべての参照は、一致する @required 宣言を持っている必要があります。」というエラーを返します。これを回避するには、これらのフィールドの `@required` ディレクティブを削除する必要があります.

上記の例では、`node` フィールドと `job` フィールドの両方で `@required` ディレクティブを削除する必要がありますが、`title` フィールドでの使用はエラーを発生させません.

fragment FriendsList on User @refetchable(queryName: "FriendsListQuery") {
friends(after: $cursor, first: $count) @connection(key: "FriendsList_friends") {
edges {
node {
job {
title @required(action: LOG)
}
}
}
}
}

FAQ

@required によって null 非許容フィールド/ルートが null 許容になったのはなぜですか?

LOG または NONE アクションを使用する場合、Relay は欠落しているフィールドをその親フィールドまたはフラグメントルートに「バブルアップ」します。これは、null 非許容フラグメントルートの子に `@required(action: LOG)`(たとえば)を追加すると、フラグメントルートの型が null 許容になることを意味します。

複数フィールドで `@required` を使用するとどうなりますか?

複数フィールドで `@required(action: LOG)` フィールドが欠落している場合、リスト内の *項目* は null として返されます。配列全体が null になることは *ありません*。動作について質問がある場合は、生成された Flow 型を調べることができます。

インラインフラグメントの @required フィールドがまだ null 許容なのはなぜですか?

次のようなフラグメントを想像してみてください。

fragment MyFrag on Actor {
... on User {
name @required(action: THROW)
}
}

ActorUser ではなく、したがって `name` が含まれていない可能性があります。それを型で表すために、次のような Flow 型を生成します。{name?: string}.

この問題が発生した場合は、次のように `__typename` を追加できます。

fragment MyFrag on Actor {
__typename
... on User {
name @required(action: THROW)
}
}

この場合、Relay は次のようなユニオン型を生成します。 {__typename: 'User', name: string} | {__typename: '%ignore this%}。これで、`__typename` フィールドをチェックして、オブジェクトの型を null 非許容の `name` を持つ型に絞り込むことができます。

なぜこれをスキーマ/サーバーレベルで実装しないのですか?

フィールドの「必須性」は実際には製品の決定であり、スキーマの質問ではありません。したがって、製品レベルでその処理を実装する必要があります。個々のコンポーネントは、欠損値を処理する方法を自分で決定できる必要があります。

たとえば、通知がマーケットプレイスリスティングの価格を表示しようとしている場合、価格を省略してもレンダリングできる可能性があります。同じリスティングの支払いフローで価格が欠落している場合は、おそらくエラーになるはずです。

もう 1 つの問題は、サーバースキーマの変更は、すべてのプラットフォームのすべての既存のクライアントに影響を与えるため、出荷がはるかに難しいことです。

基本的に、Relay によって返されるすべての値は null 許容です。これは、可能な限りフィールドレベルのエラーを処理できるようにするためです。 KillsParentOnException に頼ると、基本的にすべてのフィールドでそれを使用することを望むようになり、以前は小さかったエラーが大きくなるため、アプリはより脆くなります.

(action: NONE) をデフォルトにすることはできますか?

一方で、アクション:NONEはデフォルトとして最も理にかなっています(省略されたアクション==アクションなし)。しかし、どの値をデフォルトとして選択しても、それが最も抵抗の少ないパスであるため、エンジニアが選択するデフォルトのアクションと見なされることを認識しています。

実際には、ほとんどの場合、LOGが最も理想的な選択肢であると考えています。コンポーネントが正常に回復する機会を提供すると同時に、アプリの一部が最適ではない方法でレンダリングされているというシグナルも提供します。

そのため、LOGをデフォルトのアクションにすることを検討しましたが、それも混乱を招くと思います。

そのため、今のところデフォルトの引数は提供しない予定です。結局のところ、同等の手動ヌルチェックを書くよりもはるかに少ない記述量です。人々がどのように使用するかを確認した後、どの値(もしあれば)をデフォルトにするかを検討します。