DUICUO

Next.js の強力なライバルが登場しました! Remix が正式にオープンソース化を発表しました。

[[437227]]

皆さん、こんにちは。Pitangです。金曜日にGitHubでRemixがトレンドになっているのを見て、大きな可能性を感じました。Remixの機能について少し調べてみたので、皆さんと共有したいと思います。

最近、React Routerのオリジナルチームが開発したフルスタックWebフレームワーク「Remix」が正式にオープンソース化されました。TypeScriptとReactをベースとし、React Router V6の機能を組み込んでいます。現在、GitHubの総合トレンドリストでトップ3にランクインし、5,000以上のスターを獲得しています。

Remixはオープンソース化されて以来、Reactフルスタックフレームワーク分野で大きな話題を呼んでおり、Next.jsの強力な競合相手として間違いなく考えられます。Remixの機能は以下のとおりです。

  • 速度を優先し、次にユーザー エクスペリエンス (UX) を優先し、SSR/SSG などをサポートします。
  • HTML/CSS、HTTP、Web Front API などの基本的な Web テクノロジーに基づいており、ほとんどの場合 JavaScript なしで実行できるため、Web ブラウザー、Cloudflare Workers、サーバーレス環境、Node.js など、あらゆる環境で機能します。
  • クライアント側とサーバー側で一貫した開発エクスペリエンスを提供します。クライアント側とサーバー側のコードは単一ファイルに記述されているため、シームレスなデータ連携が可能です。さらに、TypeScriptをベースとしているため、型定義をクライアント側とサーバー側で共有できます。
  • 組み込みルートには、ルート、動的ルート、ネストされたルート、リソース ルートが含まれます。
  • 読み込み画面、スケルトン画面、その他の読み込み状態を排除します。ページ上のすべてのリソースを事前に読み込むことができるため、ページをほぼ瞬時に読み込むことができます。
  • 従来のウォーターフォール型のデータ取得方法はもう終わりです。Reactの並列処理機能と同様に、データはサーバー側で並列に取得され、完全なHTMLドキュメントが生成されます。
  • すぐに使用できる状態で、Web ページの開発に必要なすべての状態を提供します。メタデータ、スクリプト、CSS、ルーティング、およびフォーム関連のコンテンツを処理するために必要なすべてのコンポーネント (`<Links>`、`<Link>`、`<Meta>`、`<Form>`、`<Script/>` など) を提供します。
  • 予期しないエラーを処理するための `<ErrorBoundary>` や、開発者がスローしたエラーを処理するための `<CatchBoundary>` などのエラー処理が組み込まれています。

こんなにたくさんの機能があるなんてすごいですね!Remix の機能を一つずつ説明していきましょう 🚀。

一貫した開発経験

Remix はファイルベースのルーティングを提供します。これにより、データの読み取り、操作、レンダリングのロジックを同じルートファイルに記述できるため、一貫性が向上します。これにより、クライアントとサーバーのロジック間で同じ型定義セットを共有できます。

以下は公式サイトからのコードの一部です。

  1. インポートタイプ { Post }から  「~/post」 ;
  2. インポート { Outlet、Link、useLoaderData、useTransition }から  「リミックス」 ;
  3.  
  4. postsPath をpath.join (__dirname, ".." , "posts" ) とします。
  5.  
  6. 非同期関数getPosts() {
  7. dir = fs.readdir(postsPath) を待機します。
  8. Promise.allを返します(
  9. dir.map(async (ファイル名) => {
  10. file = await fs.readFile( path.join (postsPath, filename));
  11. let { 属性 } = parseFrontMatter(file.toString());
  12. 不変(
  13. isValidPostAttributes(属性)、
  14. `${filename} には不正なメタデータがあります!`
  15. );
  16. 戻る{
  17. スラッグ: ファイル名. replace (/.md$/, "" ),
  18. タイトル: 属性.title,
  19. };
  20. })
  21. );
  22. }
  23.  
  24. 非同期関数createPost(post: Post) {
  25. md = ` ---\ntitle: ${post.title}\n---\n\n${post.markdown}` とします。  
  26. fs.writeFile( path.join (postsPath, post.slug + ".md" ), md); を待機します。
  27. getPost(post.slug)を返します
  28. }
  29.  
  30. 非同期関数ローダーをエクスポートする({ request }) {
  31. getProjects()を返します
  32. }
  33.  
  34. 非同期関数をエクスポートする アクション({リクエスト}) {
  35. フォームを request.formData() で待機します。
  36. const post = createPost({ title: form.get( "title" ) });
  37. リダイレクトを返します(`/posts/${post.id}`);
  38. }
  39.  
  40. エクスポートのデフォルト 関数プロジェクト() {
  41. posts = useLoaderData<Post[]>();
  42. 状態を useTransition() とします。
  43. busy = state === "送信中"とします
  44.  
  45. 戻る
  46. <div>
  47. {posts.map((post) => (
  48. <リンク={post.slug}>{post.title}</Link>
  49. ))}
  50.  
  51. <フォームメソッド= "post" >
  52. <入力= "タイトル" />
  53. <ボタンタイプ= "送信"無効={ビジー}>
  54. {ビジー? 「作成中...」 「新しい投稿を作成」 }
  55. </ボタン>
  56. </フォーム>
  57.        
  58. <アウトレット />
  59. </div>
  60. );
  61. }

上記はルートファイルです。もしこれが src/routes/posts/index.tsx というファイルであれば、サーバーを起動して localhost:3000/posts 経由でアクセスすることでアクセスできます。これはルートファイルです。デフォルトでエクスポートされる Projects 関数はReact関数コンポーネントです。この関数によって返されるテンプレートは、このルートにアクセスするためのHTMLドキュメントです。

  • Projectsなどの各ルート関数は、GETリクエストを処理するサーバー側関数に似たローダー関数を定義できます。この関数はルート情報を取得し、サーバー側の初期レンダリングに必要なデータを提供します。この関数内では、ファイルシステムへのアクセス、データベースへのリクエスト、その他のネットワークリクエストの送信、そしてデータの返却が可能です。Projectsコンポーネントでは、Remixが提供するuseLoaderDataフックを介して、ローダー関数によって取得されたデータを取得できます。
  • 各ルート関数は、POST/PUT/PATCH/DELETEなどのGET以外のリクエストを処理する関数と同様に、実際の操作を実行するアクション関数を定義することもできます。これらの関数は、データベースの変更、ファイルシステムへの書き込みなどを行うことができ、戻り値は実際のデータ、またはredirect("/admin")のような新しいページへのリダイレクトとなります。アクション関数がデータまたはエラー情報を返す場合、Remixが提供するuseActionDataフックを使用して、返されたエラー情報を取得し、フロントエンドなどに表示することができます。

`action` 関数は `<Form method="post">` フォーム内にあり、ユーザーが送信ボタンをクリックした後に自動的に呼び出される点に注目すべきです。Remix は Fetch API を介してこの関数を呼び出し、フロントエンドを継続的にポーリングして呼び出し結果を取得し、ユーザーが複数回クリックした場合の競合を自動的に処理します。

ブラウザのネットワーク パネルには次のように表示されます。Remix は自動的に POST リクエストを開始し、/post/${post.id} へのリダイレクトを処理しながら、同時に /posts および /posts/${post.id} の対応するルート ページ コンテンツを読み込みます。

Remixが提供するuseTransitionフックを使用すると、フォームの送信ステータスを取得できます。リクエストがまだ結果を返していない場合は、この状態を使用して、ユーザーに現在のリクエストの進行状況を通知するために読み込みステータスを表示するかどうかを判断できます。

一方、PostタイプはuseLoaderDataです

ページのレンダリング、POSTリクエストの開始と作成、バックエンドでのPOSTリクエストの作成、そしてPOST詳細へのリダイレクトというプロセス全体を通して、フロントエンドでJavaScript関連のコンテンツを一切使用していないことに気づいた学生もいるかもしれません。このインタラクションはHTMLとHTTPのみで完了しています。そのため、RemixウェブサイトはJavaScriptが無効なランタイム環境でも正常に動作します。

上の画像からわかるように、JavaScript が無効になっていても、当社の Web サイトは正常に機能します。

強力なネストルーティングシステム

「ファイルをルートとして扱う」というコンセプトに基づいているため、ルート定義を一元的に管理する必要はありません。対応するファイルを作成すると、Remix が自動的に対応するルートを登録します。

Remix の最も特徴的な機能の一つは、ネストされたルーティングです。Remix では、通常、ページには複数レベルのページが含まれ、各ページは独自の UI 表示を制御し、データの読み込みとコードの分割を独立して管理します。

公式サイトからの例を見てみましょう。

上記ページ間の対応は以下のとおりです。

  • ページ モジュール全体は "/" で表され、"/sales" は右側の水色の領域全体、"/sales/invoices" は黄色のセクション、"/sales/invoices/102000" は右下隅の赤いセクションに対応します。

ルーティング全体は階層化されており、ページ全体の階層化されたビューに対応しています。各レイヤーのコードは独立して記述され、ビューは独立してレンダリングされ、データは独立して取得され、エラーは独立して表示されます。

実際の例を見てみましょう。

  1. // src/root.tsx
  2. 輸入 {
  3. 出口
  4.    
  5. エクスポートのデフォルト 関数App() {
  6. 戻る
  7. <文書>
  8. <レイアウト>
  9. <アウトレット />
  10. </レイアウト>
  11. </ドキュメント>
  12. );
  13. }
  14.  
  15. 関数Document() {}
  16. 関数レイアウト() {}
  1. // src/routes/admin.tsx
  2. インポート { Outlet, Link, useLoaderData }から  「リミックス」 ;
  3. インポート{ getPosts }   「~/post」 ;
  4. インポートタイプ { Post }から  「~/post」 ;
  5. adminStylesをインポートする  "~/styles/admin.css" ;
  6.  
  7. エクスポートlet links = () => {
  8. [{ rel: "スタイルシート" , href: adminStyles }]を返します
  9. };
  10.  
  11. エクスポート let loader = () => {
  12. getPosts()を返します
  13. };
  14.  
  15. エクスポートのデフォルト 関数Admin() {
  16. posts = useLoaderData<Post[]>();
  17. 戻る
  18. <div クラス名 = "管理者" >
  19. <ナビ>
  20. <h1>管理者</h1>
  21. <ul>
  22. {posts.map((post) => (
  23. <liキー={post.slug}>
  24. <リンク={post.slug}>{post.title}</Link>
  25. </li>
  26. ))}
  27. </ul>
  28. </nav>
  29. <メイン>
  30. <アウトレット />
  31. </メイン>
  32. </div>
  33. );
  34. }
  1. // src/routes/admin/インデックス.tsx
  2. インポート { リンク }から  「リミックス」 ;
  3.  
  4. エクスポートのデフォルト 関数AdminIndex() {
  5. 戻る
  6. <p>
  7. <Link to = "new" >新しい投稿を作成</Link>
  8. </p>
  9. );
  10. }
  1. // src/routes/admin/new.tsx
  2. useTransition、useActionData、redirect、Form をインポートします  「リミックス」 ;
  3. インポート型 { ActionFunction }から  「リミックス」 ;
  4. インポート{ createPost }   「~/post」 ;
  5. 変量をインポート  「小さな不変量
  6.  
  7. エクスポートletアクション:ActionFunction = async({request}) => {
  8. 新しい Promise((res) => setTimeout(res, 1000)) を待機します。
  9. let formData = await request.formData();
  10.  
  11. title = formData.get( "title" );
  12. slug = formData.get( "slug" );
  13. markdown = formData.get( "markdown" );
  14.  
  15. エラーを {} とします。
  16. if (!title) errors.title = true ;
  17. if (!slug) errors.slug = true ;
  18. error.markdown がtrue の場合;
  19.  
  20. if (Object.keys(errors).length) {
  21. エラーが返されました
  22. }
  23.  
  24. createPost({ タイトル、スラッグ、マークダウン }) を待機します。
  25.  
  26. リダイレクトを返します( "/admin" );
  27. };
  28.  
  29. エクスポートのデフォルト 関数NewPost() {
  30. エラーを useActionData() に設定します。
  31. transition を useTransition() にします。
  32.  
  33. 戻る
  34. <フォームメソッド= "post" >
  35. <p>
  36. <ラベル>
  37. 投稿タイトル: {errors?.title && <em>タイトル必須</em>}
  38. <入力タイプ= "テキスト"  名前= "タイトル" />
  39. </ラベル>
  40. </p>
  41. <p>
  42. <ラベル>
  43. 投稿スラッグ: {errors?.slug && <em>スラッグは必須です</em>}{ " " }
  44. <入力タイプ= "テキスト"  名前= "スラッグ" />
  45. </ラベル>
  46. </p>
  47. <p>
  48. <label htmlFor= "markdown" >マークダウン:</label>{ " " }
  49. {errors?.markdown && <em>Markdown が必要です</em>}
  50. <br />
  51. <textarea rows ={20} name = "マークダウン" />
  52. </p>
  53. <p>
  54. <ボタンタイプ= "送信" >
  55. {transition.submission ? "作成..." : "投稿を作成" }
  56. </ボタン>
  57. </p>
  58. </フォーム>
  59. );
  60. }

上記のコードによってレンダリングされるページは次のようになります。

アプリのウェブサイト全体は、`<Document>` 要素と `<Layout>` 要素がネストされた構造になっています。`<Outlet>` 要素は、上の図で緑色で示されているように、ルートが埋め込まれる場所です。`localhost:3000/` にアクセスすると、埋め込まれるコンテンツは `src/routes/index.tsx` ルートファイルに対応するレンダリングされたコンテンツであり、`localhost:3000/admin` にアクセスすると、埋め込まれるコンテンツは `src/routes/admin.tsx` ルートファイルのレンダリングされたコンテンツです。

`src/routes/admin.tsx` ファイルは引き続き `<Outlet>` ルートコンポーネントを提供します。つまり、`http://localhost:3000/admin/new` へのアクセスなど、階層構造(ネスト)のルートを追加すると、この `<Outlet>` は対応するルートファイル `src/routes/admin/new.tsx` の内容をレンダリングします。逆に、`http://localhost:3000/admin` にアクセスすると、`<Outlet>` セクションは対応するルートファイル `src/routes/admin/index.tsx` の内容をレンダリングします(下の図を参照)。

このネストされたルーティングは自動的に行われます。`src/routes/admin.tsx` というファイルを作成し、同じ名前のフォルダを作成し、さらにそのフォルダ内に他のファイルを作成すると、これらのファイル名が次のレベルのネストされたルートの名前として登録されます。

  • localhost:3000/admin src/routes/admin.tsxとsrc/routes/admin/index.tsxを同時に登録する
  • localhost:3000/admin/new src/routes/admin/new.tsx を登録する

このアプローチでは、ファイルをルートとして使用し、同じ名前のフォルダー内にネストされたルートを使用して、ルートを親ページ内に実装します。この方法では、サブルートに基づいてサブページのコンテンツをレンダリングすることで、柔軟性が大幅に向上します。各サブルートは独立したルートファイルに対応し、独立したデータ処理ロジック、コンテンツレンダリングロジック、エラー処理ロジックを備えています。

上で説明したネストされたルーティングの明らかな利点の 1 つは、特定の部分でエラーが発生した場合に、ErrorBoundary セクションと CatchBoundary セクション (後述) でエラー ページを表示できる一方で、ユーザーはページ全体を更新して再読み込みすることなく他の部分を操作できるため、Web サイトのフォールト トレランスが大幅に向上することです。

さようなら、読み込み状態

ネストされたルートを使用することで、Remix はほぼすべての読み込み状態とスケルトン画面を排除できます。現在、多くのアプリケーションはフロントエンドコンポーネント内でデータを取得し、最初のデータを取得した後、そのデータを使用して後続のデータを取得するという、ウォーターフォール型のフェッチパターンを形成しています。データ量が多いと、ページの読み込みに時間がかかります。そのため、多くのウェブサイトでは、ユーザーエクスペリエンスを向上させるために、回転するホイールなどの読み込み状態やスケルトン画面を組み込んでいます。以下はその例です。

これは、これらのアプリケーションにはRemixのようなネストされたルートの概念が欠けているためです。ルートにアクセスすると、そのルートに対応するページにアクセスします。ページが読み込まれ、その中の子コンポーネントがレンダリングされた後にのみ、データが取得され、子コンポーネントが再度読み込まれます。このプロセスが繰り返されるため、ウォーターフォール型の読み込みと、多くの中間読み込み状態が発生します。

Remixはネストされたルーティングを提供します。localhost:3000/admin/new ルートにアクセスすると、3階層のルートがロードされます。同時に、これら3つのルートに対応するページが独立して並列にロードされ、データも独立して並列に取得されます。最終的に、次のように完全なHTMLドキュメントがクライアントに送信されます。

ご覧の通り、最初の画面にコンテンツが表示されるまでに少し時間がかかるかもしれませんが、読み込み状態になる必要はもうありません。デイジー型の画面👋🏻、スケルトン画面👋🏻とはお別れです。

[[437231]]

さらに、Remix はネストされたルートを活用し、リンクにマウスオーバーした際に、クリックしてサブルートを切り替えるための事前準備としてプリフェッチ機能を提供します。これにより、サブルートのドキュメントや、CSS、画像、関連データなど、様々なリソースを事前に取得できます。これにより、実際にリンクをクリックしてサブルートを切り替える際に、ページが即座に表示されます。

エラー処理の改善

当社のウェブサイトは頻繁に問題が発生しています。他のフレームワークを使用すると、問題が発生した際にユーザーがウェブサイトを更新する必要があるかもしれません。しかし、Remixではネストされたルートの概念に基づいているため、更新の必要はありません。エラーメッセージは、問題のあるサブルートに表示されるだけで、ページの残りの部分は正常に機能し続けます。

例えば、上の画像の右下にあるサブルートに問題がある場合、問題が発生するとこの部分にはエラー ページが表示されますが、ページの他の部分には通常の情報が表示されます。

さまざまなクライアント側およびサーバー側のエラー、予期されるエラーと予期されないエラーなど、エラーは頻繁に発生し、処理が非常に困難であるため、Remix には、React の ErrorBoundary に似た概念を提供する堅牢なエラー処理メカニズムが組み込まれています。

Remix では、各ルーティング関数は ErrorBoundary 関数に対応します。

  1. エクスポートのデフォルト 関数RouteFunction() {}
  2.  
  3. エクスポート関数ErrorBoundary({ error }) {
  4. コンソール.error(エラー);
  5. 戻る
  6. <div>
  7. <h2>やられた!</h2>
  8. <p>
  9. この請求書の読み込み中に問題が発生しました
  10. </p>
  11. </div>
  12. );
  13. }

ErrorBoundary関数は、ローダー、アクション、クライアント、またはサーバーからの予期しないエラーを処理します。予期しないエラーが発生すると、この関数が起動され、エラーを示す対応するUIメッセージが表示されます。

各ルーティング関数は CatchBoundary 関数にも対応しています。

  1. useCatch をインポートする  「リミックス」 ;
  2.  
  3. エクスポート関数CatchBoundary() {
  4. catch() を useCatch() とします。
  5.  
  6. 戻る
  7. <div>
  8. <h1>捕まった</h1>
  9. <p>ステータス: {caught.status} </p>
  10. <前>
  11. <code>{JSON.stringify(caught.data, null , 2)} </code>
  12. </pre>
  13. </div>
  14. );
  15. }

CatchBoundary関数は、クライアントまたはサーバー上でローダーまたはアクション関数で手動でスローされるレスポンスエラー、つまり想定されるエラーに対応します。これらのエラーのパスは予測可能です。CatchBoundaryでは、スローされたレスポンスエラーはuseCatchフックを通じて取得され、対応するエラー情報がUIに表示されます。

子ルートにErrorBoundary関数またはCatchBoundary関数を追加しないと、エラーは次の上位レベルのルート、つまり最上位ルートページまで伝わってしまいます。したがって、最上位ルートファイルではErrorBoundary関数とCatchBoundary関数をそれぞれ1つだけ宣言し、起こり得るすべてのエラーを捕捉してコードレビュー中に迅速に特定できるようにすることが最善です。

Webインフラストラクチャ技術に基づく

Remix は、フルスタック Web 開発フレームワークに必要なすべての状態と基本コンポーネントを提供しながら、HTML/CSS や HTTP などの基本的な Web テクノロジを使用して問題を解決することに重点を置いています。

関連する州は次のとおりです。

  1. // データの読み込みステータス
  2. 使用ローダーデータ()
  3.  
  4. // データステータスを更新する
  5. アクションデータの使用()
  6.  
  7. // フォームの送信と関連ステータス
  8. フォームアクション()を使用する
  9. 送信()を使用する
  10.  
  11. // 統合された読み込み状態
  12. トランジションを使用する()
  13.  
  14. // エラーキャプチャステータスなど
  15. キャッチ()を使う

ウェブサイトを構成する基本的なコンポーネントは次のとおりです。

  • <Meta> は、Web ページのメタデータを動的に設定するために使用され、SEO に役立ちます。
  • `<Script>` 属性は、Web ページを読み込むときに関連する JavaScript をインポートするかどうかを Remix に指示します。ほとんどの場合、Remix で作成されたページは JavaScript がなくても正しく機能するためです。
  • `<Form>` タグはネイティブの `<form>` タグを置き換え、クライアント側とサーバー側の両方でフォーム操作を容易にします。送信レスポンス、Fetch API を使用したリクエストを処理し、複数の送信によって発生する競合状態を管理します。

さらに、ルーティング機能を含むファイル内で、`link`、`meta`、`links`、`headers` などの関数を宣言することで、対応する機能を宣言できます。

  • `links` 変数関数は、CSS や画像など、このページに読み込む必要があるリソースを表します。
  1. インポートタイプ { LinksFunction }から  「リミックス」 ;
  2. スタイルHrefインポート  「../styles/something.css」 ;
  3.  
  4. リンクをエクスポート: LinksFunction = () => {
  5. 戻る[
  6. //ファビコンを追加する
  7. {
  8. rel: "アイコン"
  9. href: "/favicon.png" ,
  10. タイプ: "image/png"  
  11. },
  12.  
  13. //外部スタイルシートを追加する
  14. {
  15. rel: "スタイルシート"
  16. href: "https://example.com/some/styles.css" ,
  17. クロスオリジン: "true"  
  18. },
  19.  
  20. //ローカルスタイルシートを追加すると、リミックスファイル名のフィンガープリントを作成します のために 
  21. // プロダクションキャッシュ
  22. { rel: "スタイルシート" , href: stylesHref },
  23.  
  24. //ユーザーがブラウザキャッシュ画像をプリフェッチする 見る可能性高い
  25. // このページ操作する、ボタンをクリックする  
  26. // 概要/詳細要素
  27. {
  28. rel: "プリフェッチ"
  29. として 「画像」
  30. href: "/img/bunny.jpg"  
  31. },
  32.  
  33. //大きい画面場合のみプリフェッチする
  34. {
  35. rel: "プリフェッチ"
  36. として 「画像」
  37. href: "/img/bunny.jpg"
  38. メディア: "(最小幅: 1000px)"  
  39. }
  40. ];
  41. };
  • `links` 関数は、プリフェッチする必要があるページを宣言し、ユーザーがクリックする前にリソースが読み込まれるようにします。
  1. エクスポート関数links() {
  2. [{ page: "/posts/public" }]を返します
  3. }
  • メタ関数: コンポーネントと同様に、ページに必要なメタ情報を宣言します。
  1. インポート型 { MetaFunction }から  「リミックス」 ;
  2.  
  3. エクスポート let meta: MetaFunction = () => {
  4. 戻る{
  5. title: "Josie's Shake Shack" , // <title>Josie's Shake Shack</title>
  6. description: "おいしいシェイク" , // <meta name = "description" content= "おいしいシェイク" >
  7. "og:image" : "https://josiesshakeshack.com/logo.jpg" // <meta property= "og:image" content= "https://josiesshakeshack.com/logo.jpg" >
  8. };
  9. };
  • headers 関数は、このページが HTTP リクエストを送信するときに含める必要があるリクエスト ヘッダーを定義します。
  1. エクスポート関数ヘッダー({ loaderHeaders, parentHeaders }) {
  2. 戻る{
  3. 「X-ストレッチパンツ」 「楽しみのためです」
  4. 「キャッシュ制御」 : 「max-age=300、s-maxage=3600」  
  5. };
  6. }

ご覧のとおり、Remix はフルスタック Web 開発ライフサイクル全体に必要なほぼすべてのものを提供し、最小限の労力で高性能で高品質の Web サイトを開発できるようにするためのベスト プラクティスも含まれています。

もちろん、この記事ではRemixの機能を全て網羅することはできません。ここまで読んでRemixにまだご興味がおありでしたら、公式サイト(https://remix.run/)で詳細をご確認ください。公式サイトでは、Remixを使った実用的なアプリケーション開発に役立つ、非常に詳細なチュートリアルを提供しています。

Remix の機能について理解できたので、どう思いますか?Next.js を超えることができると思いますか?🐴