Relay Hooksと従来のコンテナAPI
Relay Hooksとコンテナの互換性
Relay HooksはRelayのコンテナベースのAPIと完全に互換性があります。つまり、コンテナはHooksを使用するコンポーネントをレンダリングでき、その逆も可能です。
これは、既存のアプリケーションの残りの部分に影響を与えることなく、新しいコードにのみ使用するか、アプリケーションの特定の部分を移行することにより、Relay Hooksを段階的に導入できることを意味します。
既存のコンテナベースのコードの移行
前述のとおり、既存のコードをRelay Hooksに移行する必要はありません。また、コンテナベースのコードは引き続き動作します。
ただし、このセクションでは、コンテナベースのコードをRelay Hooksに移行することを選択した場合に、従うことができる一般的な移行パターンについて説明します。
QueryRenderer
→ useLazyLoadQuery
QueryRenderer
からuseLazyLoadQuery
Hookへの変換は、最も簡単な変換であり、レンダリング中に指定されたクエリをフェッチするという同様の動作になります。
QueryRenderer
をuseLazyLoadQuery
に変換するには、次の手順を実行する必要があります。
- QueryRendererがあった場所、またはその上に
RelayEnvironmentProvider
をレンダリングします。通常は、アプリケーションのルートにRelayEnvironmentProvider
をレンダリングすることをお勧めします。
<RelayEnvironmentProvider environment={MyAppEnvironment}>
<App />
</RelayEnvironmentProvider>
QueryRenderer
をuseLazyLoadQuery
に変換します。
変換前
import * as React from 'React';
import {graphql, QueryRenderer} from 'react-relay';
export default function Home() {
return (
<QueryRenderer
environment={MyAppEnvironment}
query={graphql`
query HomeQuery($id: ID!) {
user(id: $id) {
name
}
}
`}
variables={{id: 4}}
render={(props, error) => {
if (error) {
return <Error />;
}
if (!props) {
return <Loading />;
}
return <h1>{props.user?.name}</h1>
}}
/>
);
}
変換後: クエリをフェッチしてレンダリングします
import * as React from 'React';
import {graphql, useLazyLoadQuery} from 'react-relay';
export default function Home() {
const data = useLazyLoadQuery(
graphql`
query HomeQuery($id: ID!) {
user(id: $id) {
name
}
}
`,
{id: 4},
);
return <h1>{data.user?.name}</h1>;
}
ローディング状態とエラー状態は、SuspenseとError Boundariesによって処理されます。
<ErrorBoundary renderError={Error}>
<Suspense fallback={<Loading />}>
<Home />
</Suspense>
</ErrorBoundary>
QueryRenderer
→ useQueryLoader
+ usePreloadedQuery
useLazyLoadQuery
とは異なり、useQueryLoader
とusePreloadedQuery
を組み合わせて使用すると、レンダリングの前にデータのフェッチが開始され、「render-as-you-fetch」パターンに従います。これは、データのフェッチがより早く開始され、ユーザーにコンテンツを表示するまでの時間が短縮される可能性があることを意味します。
このパターンを最大限に活用するには、通常、クエリロードをルーターレベル、またはUIインフラストラクチャの他の部分に統合します。完全な例については、issue-tracker
サンプルアプリケーションを参照してください。
QueryRenderer
をuseQueryLoader
に変換するには、次の手順を実行する必要があります。
- QueryRendererがあった場所、またはその上に
RelayEnvironmentProvider
をレンダリングします。通常は、アプリケーションのルートにRelayEnvironmentProvider
をレンダリングすることをお勧めします。
<RelayEnvironmentProvider environment={MyAppEnvironment}>
<App />
</RelayEnvironmentProvider>
QueryRenderer
をuseQueryLoader
+usePreloadedQuery
に変換します
変換前
import * as React from 'React';
import {graphql, QueryRenderer} from 'react-relay';
export default function UserPopover() {
return (
<QueryRenderer
environment={MyAppEnvironment}
query={graphql`
query UserPopoverQuery($id: ID!) {
user(id: $id) {
name
}
}
`}
variables={{id: 4}}
render={(props, error) => {
if (error) {
return <Error />;
}
if (!props) {
return <Loading />;
}
return <h1>{props.user?.name}</h1>
}}
/>
);
}
変換後: プリロードされたクエリをレンダリングします
import * as React from 'React';
import {graphql, usePreloadedQuery} from 'react-relay';
export default function UserPopover(props) {
const data = usePreloadedQuery(
graphql`
query UserPopoverQuery($id: ID!) {
user(id: $id) {
name
}
}
`,
props.queryRef,
);
return <h1>{data.user?.name}</h1>;
}
useQueryLoader
のloadQuery
でクエリを読み込みます。コードのこの部分は通常、ルーティング、またはUIインフラストラクチャの他の部分に統合されます。
import * as React from 'React';
import {useQueryLoader} from 'react-relay';
// Import the query defined in the UserPopover component
import UserPopoverQuery from '__generated__/UserPopoverQuery.graphql';
// This is *NOT* a real-world example, only used
// to illustrate usage.
export default function UserPopoverButton(props) {
const [queryRef, loadQuery] = useQueryLoader(UserPopoverQuery)
const handleClick = useCallback(() => {
// Load the query in the event handler, onClick
loadQuery({id: props.userID})
}, [loadQuery, props.userID]);
return (
<>
<Button onClick={handleClick} />
{queryRef != null ?
<Popover>
{/* Loading and error states are handled by
Suspense and Error Boundaries */}
<ErrorBoundary renderError={Error}>
<Suspense fallback={<Loading />}>
{/*Pass the queryRef*/}
<UserPopover queryRef={queryRef} />
</Suspense>
</ErrorBoundary>
</Popover>
: null
}
</>
);
}
フラグメントコンテナ → useFragment
フラグメントコンテナは、useFragment
呼び出しに直接マッピングされます。
変換前
import * as React from 'React';
import {graphql, createFragmentContainer} from 'react-relay';
function UserComponent(props: Props) {
const user = props.user;
return (
<>
<h1>{user.name}</h1>
<div>
<img src={user.profile_picture?.uri} />
</div>
</>
);
}
export default createFragmentContainer(UserComponent, {
user: graphql`
fragment UserComponent_user on User {
name
age
profile_picture(scale: 2) {
uri
}
}
`,
});
変換後
import * as React from 'React';
import {graphql, useFragment} from 'react-relay';
export default function UserComponent(props: Props) {
const data = useFragment(
graphql`
fragment UserComponent_user on User {
name
profile_picture(scale: $scale) {
uri
}
}
`,
props.user,
);
return (
<>
<h1>{data.name}</h1>
<div>
<img src={data.profile_picture?.uri} />
</div>
</>
);
}
再フェッチコンテナ → useRefetchableFragment
useRefetchableFragment
の再フェッチAPIは、以前の再フェッチコンテナと比較して簡素化および削減されています。移行するには、入力を新しいAPIにマッピングする必要があります。
変換前
import * as React from 'React';
import {graphql, createRefetchContainer} from 'react-relay';
function CommentBody(props: Props) {
const relay = props.relay;
return (
<>
<p>{data.body?.text}</p>
<Button
onClick={() => relay.refetch(
{lang: 'SPANISH'}, // fragmentVariables
null, // renderVariables
error => { ... },
{force: true}
)}>
Translate Comment
</Button>
</>
);
}
export default createRefetchContainer(
CommentBody,
{
user: graphql`
fragment CommentBody_comment on Comment {
body(lang: $lang) {
text
}
}
`,
},
// This option is no longer required, the refetch query
// will automatically be generated by Relay using the @refetchable
// directive.
graphql`
query AppQuery($id: ID!, lang: Lang) {
node(id: $id) {
...CommentBody_comment
}
}
`,
);
変換後
import * as React from 'React';
import {graphql, useRefetchableFragment} from 'react-relay';
export default function CommentBody(props: Props) {
const [data, refetch] = useRefetchableFragment(
graphql`
fragment CommentBody_comment on Comment
@refetchable(queryName: "CommentBodyRefetchQuery") {
body(lang: $lang) {
text
}
}
`,
props.comment,
);
const handleClick = useCallback(() => {
refetch({lang: 'SPANISH'});
}, [refetch]);
return (
<>
<p>{data.body?.text}</p>
<Button
onClick={handleClick}>
Translate Comment
</Button>
</>
);
}
ページネーションコンテナ → usePaginationFragment
usePaginationFragment
のページネーションAPIは、以前のPaginationContainerと比較して大幅に簡素化および削減されています。移行するには、入力を新しいAPIにマッピングする必要があります。
変換前
import * as React from 'React';
import {graphql, createPaginationContainer} from 'react-relay';
class UserContainerComponent extends React.Component {
render(): React.Node {
const isLoading = this.props.relay.isLoading() || this.state.loading;
const hasMore = this.props.relay.hasMore();
return (
<>
<FriendsList friends={this.props.user?.friends} />
<Button
onClick={() => this.loadMore()}
disabled={!hasMore || isLoading}>
Load More
{isLoading && <InlineSpinner />}
</Button>
</>
);
}
loadMore() {
if (
!this.props.relay.hasMore() ||
this.props.relay.isLoading() ||
this.state.loading
) {
return;
}
this.setState({loading: true});
this.props.relay.loadMore(5, () => this.setState({loading: false}));
}
}
export default createPaginationContainer(
UserContainerComponent,
{
user: graphql`
fragment UserContainerComponent_user on User
@argumentDefinitions(count: {type: "Int!"}, cursor: {type: "ID"})
@refetchable(queryName: "UserComponentRefetchQuery") {
friends(first: $count, after: $cursor)
@connection(key: "UserComponent_user_friends") {
edges {
node {
name
}
}
}
}
`,
},
{
// This option is no longer necessary, usePaginationFragment supports
// bi-directional pagination out of the box.
direction: 'forward',
// This option is no longer required, and will be automatically
// determined by usePaginationFragment
getConnectionFromProps(props: Props) {
return props.user?.friends;
},
// This option is no longer required, and will be automatically
// determined by usePaginationFragment
getFragmentVariables(vars, count) {
return {...vars, count};
},
// This option is no longer required, and will be automatically
// determined by usePaginationFragment
getVariables(props: Props, {count, cursor}) {
return {
cursor,
count,
};
},
// This option is no longer required, the pagination query
// will automatically be generated by Relay using the @refetchable
// directive.
query: graphql`
query UserContainerComponentQuery {
viewer {
actor {
... on User {
...UserContainerComponent_user @arguments(count: 10)
}
}
}
}
`,
},
);
変換後
import * as React from 'React';
import {graphql, usePaginationFragment} from 'react-relay';
export default function UserComponent(props: Props) {
const {data, loadNext, hasNext, isLoadingNext} = usePaginationFragment(
graphql`
fragment UserComponent_user on User
@refetchable(queryName: "UserComponentRefetchQuery") {
friends(first: $count, after: $after)
@connection(key: "UserComponent_user_friends") {
edges {
node {
name
}
}
}
}
`,
props.user,
);
const handleClick = useCallback(() => {
loadNext(5)
}, [loadNext])
return (
<>
<FriendsList friends={data?.friends?.edges} />
<Button onClick={handleClick} disabled={!hasNext || isLoadingNext}>
Load More
{isLoadingNext && <InlineSpinner />}
</Button>
</>
);
}
QueryRenderer → useEntryPointLoader + EntryPointContainer
TODO(未対応)
commitMutation → useMutation
TODO(未対応)
requestSubscription → useSubscription
TODO(未対応)
このページはお役に立ちましたか?
いくつかの簡単な質問に答えることで、 サイトの改善にご協力ください。.