このブログ投稿では、基本的な React アプリケーションを して するために必要なすべてのことを説明します。 React を使い始めたばかりの初心者でも、スキルを磨きたい経験豊富な開発者でも、このガイドは最適です。 理解 構築 このガイドでは、設計、レイアウト、状態管理など、完全に機能する To Do リスト アプリを構築するプロセス全体について説明します。関数コンポーネントとフックを使用します。 state と props を使用してコンポーネント間でデータを渡す方法と、ユーザー入力を処理してアプリの状態を更新する方法を学びます。 このガイドの終わりまでに、React アプリをゼロから構築する方法をしっかりと理解できるようになり、新たに得た知識を利用して独自の React プロジェクトを構築できるようになります。 それでは、始めましょう! *これから作成するアプリのコードは で、ライブ バージョン 。 こちら はこちらでご覧いただけます 簡単な紹介 コードの記述には TypeScript を使用し、アプリの開発と構築には Vite を使用します。 TypeScript は、JavaScript に基づいて構築された、厳密に型指定されたプログラミング言語です。実際には、すでに JavaScript を知っている場合、TypeScript の使用方法を学ぶ必要があるのは、型とインターフェイスの使用方法だけです。 TypeScript 型とインターフェイスにより、コードで使用しているデータ型を定義できます。これにより、バグを早期に発見し、問題を回避することができます。 たとえば、関数が を取って を渡すと、TypeScript はすぐに文句を言います。 number string const someFunc = (parameter: number) => {...}; someFunc('1') // Argument of type 'string' is not assignable to parameter of type 'number'. JavaScript を使用していた場合、後でバグを発見することになるでしょう。 TypeScript は多くの場合自動的に型を推測できるため、常に型を指定する必要はありません。 で TypeScript の基本を学ぶことができます。 (または単に型を無視します。) ここ バイト React アプリケーションを起動する最も一般的な方法は、おそらく を使用することです。代わりに (「ヴィート」と発音) を使用します。しかし、心配する必要はありません。シンプルですが、より効率的です。 create-react-app Vite (内部で create-react-app によって使用される) などのツールを使用すると、アプリケーション全体を単一のファイルにバンドルしてからブラウザーに提供する必要があります。一方、Vite はブラウザーのネイティブ ES モジュールを利用して、必要に応じてソース コードの一部を提供する でバンドルをより効率的にします。 webpack Rollup また、Vite は Hot Module Replacement を使用して開発時間を大幅に短縮できます。つまり、ソース コードに変更が加えられるたびに、アプリケーション全体ではなく、変更のみが更新されます。 それに加えて、Vite は Typescript、JSX および TSX、CSS などのネイティブ サポートを提供します。 create-react-app と同様に、Vite は create-vite というツールを提供しています。これにより、Vanilla JS のオプションを含む基本的なテンプレートを使用したり、React などのライブラリを使用したりして、新しいプロジェクトをすばやく開始できます。 明確にするために言うと、React アプリケーションをビルドするのに Vite や create-react-app のようなツールは ませんが、プロジェクトのセットアップ、コードのバンドル、トランスパイラーの使用などを処理してくれるので、作業が楽になります。 必要あり React に飛び込む JSX / TSX React を使用すると、コードにマークアップを直接追加できます。これは後でプレーンな JavaScript にコンパイルされます。これは と呼ばれます。 JSX を使用している場合、JavaScript の場合は .jsx、TypeScript の場合は .tsx としてファイルを保存できます。 JSX 次のようになります。 const element = <h1>Hello, world!</h1>; HTML に似ていますが、JavaScript ファイルに埋め込まれており、プログラミング ロジックでマークアップを操作できます。中括弧内にある限り、JSX 内に JavaScript コードを追加することもできます。 たとえば、異なる段落要素としてレンダリングしたいテキストの配列がある場合、次のようにすることができます。 const paragraphs = ["First", "Second", "Third"]; paragraphs.map((paragraph) => <p>{paragraph}</p>); そして、次のようにコンパイルされます。 <p>First</p> <p>Second</p> <p>Third</p> しかし、それだけをしようとしてもうまくいきません。これは、React がコンポーネントで動作し、これらのコンポーネント内で JSX をレンダリングする必要があるためです。 React コンポーネント React コンポーネントは、JavaScript または単純な を使用して記述できます。関数コンポーネントは最新であり、現在 React コンポーネントを記述するための推奨される方法であるため、ここでは関数コンポーネントに焦点を当てます。 クラス 関数 コンポーネントは、ブラウザーによってコンパイルおよびレンダリングされる JSX を返す関数によって定義されます。上記の例を拡張して、段落要素をレンダリングする場合は、次のようになります。 // Define the component const Component = () => { const paragraphs = ["First", "Second", "Third"]; return ( <> {paragraphs.map((paragraph) => ( <p>{paragraph}</p> ))} </> ); }; // Use the component in the same way you use an HTML element in the JSX const OtherComponent = () => { return <Component />; }; 小道具 ここで、このコンポーネントを別の情報で再利用したいと思うかもしれません。 props を使用してそれを行うことができます — これは、何らかのデータを保持する単なる JavaScript オブジェクトです。 この例では、配列をハードコーディングする代わりに、それをコンポーネントに渡すことができます。結果は同じになりますが、コンポーネントは再利用可能になります。 TypeScript を使用している場合は、props オブジェクト内のデータの型を指定する必要があります (それらが何であるかについてのコンテキストがないため、TypeScript はそれらを推測できません)。この場合、これは文字列の配列 ( )。 string[] const Component = (props: { paragraphs: string[] }) => { <> {props.paragraphs.map((paragraph) => ( <p>{paragraph}</p> ))} </>; }; const OtherComponent = () => { const paragraphs = ["First", "Second", "Third"]; return <Component paragraphs={paragraphs} />; }; 州 インタラクティブなコンポーネントを作成したい場合は、コンポーネントの状態に情報を保存する必要があります。これにより、コンポーネントはそれを「記憶」できます。 たとえば、ボタンがクリックされた回数を示す単純なカウンターを定義したい場合、この値を保存および更新する方法が必要です。 React では、 フックを使用してそれを行うことができます ( は、React に「フック」できる関数です)。 useState フック の状態とライフサイクル機能 初期値で フックを呼び出すと、値自体とそれを更新する関数を含む配列が返されます。 useState import { useState } from "react"; const Counter = () => { const [count, setCount] = useState(0); return ( <> <span>{count}</span> <button onClick={() => setCount(count + 1)}>Increment count</button> </> ); }; この知識があれば、React アプリの構築を開始する準備が整いました。 プロジェクトの作成 依存関係 Vite を使用するには、**ノード**とパッケージ マネージャーが必要です。 ノードをインストールするには、システムと構成に応じて、 でオプションの 1 つを選択するだけです。 Linux または Mac を使用している場合は、 を使用してインストールすることもできます。 ここ Homebrew パッケージ マネージャーは または です。この投稿では、 を使用します。 npm yarn npm プロジェクトの作成 次に、プロジェクトを作成します。ターミナルで、プロジェクトが作成されるディレクトリに移動し、create-vite コマンドを実行します。 $ npm create vite@latest 追加のパッケージ (create-vite など) をインストールするように求められる場合があります。 と入力し、Enter キーを押して続行します。 y Need to install the following packages: create-vite@4.0.0 Ok to proceed? (y) 次に、プロジェクト情報を入力するよう求められます。 プロジェクトの を入力します。 を選択しました。 名前 my-react-project ? Project name: › my-react-project 「フレームワーク」として を選択します。 React React は技術的 ではありませんが、心配する必要はありません。 にはライブラリであり、フレームワーク ? Select a framework: › - Use arrow-keys. Return to submit. Vanilla Vue ❯ React Preact Lit Svelte Others をバリアントとして選択します。 TypeScript + SWC (Speedy Web Compiler の略) は、Rust で書かれた超高速の TypeScript / JavaScript コンパイラです。彼らは、「シングル スレッドで Babel より 20 倍、4 コアで 70 倍速い」と主張しています。 SWC ? Select a variant: › - Use arrow-keys. Return to submit. JavaScript TypeScript JavaScript + SWC ❯ TypeScript + SWC これで、プロジェクトが作成されました。開発モードで起動するには、プロジェクト ディレクトリに移動し、依存関係をインストールして、dev スクリプト コマンドを実行する必要があります。 cd my-react-project npm install npm run dev 数秒後、次のようなものが表示されます。 VITE v4.0.4 ready in 486 ms ➜ Local: http://localhost:5173/ ➜ Network: use --host to expose ➜ press h to show help ブラウザーを開いて / に移動すると、デフォルトの Vite + React ページが表示されます。 http://localhost:5173 これは、すべてが正常であることを意味し、アプリの作業を開始できます。 アプリの構築 ファイル構造と初期設定 コード エディターまたは選択した IDE でプロジェクトを開くと、次のようなファイル構造が表示されます。 ボイラープレート ファイルの一部は使用しないため、削除できます (すべての .svg および .css ファイル)。 App 関数のコードを削除すると、次のようになります。 function App() { return ( ) } export default App このファイルについては後で説明します。 スタイリング ここではスタイリングに焦点を当てていませんが、Tailwind CSS を使用します。これは、HTML 要素にクラスを追加してスタイルを設定できるライブラリです。以下 に従って、独自のプロジェクトに反映されたスタイルを確認してください。 の手順 それ以外の場合は、コード内のクラスを無視できます。 デザインを考える:部品配置 設計プロセスは、アプリの開発に不可欠な部分であり、見落とされるべきではありません。 To Do リスト アプリを作成するには、まずコンポーネントのレイアウトを考える必要があります。 まず、基本的な UI をモックし、関連するコンポーネントの階層を概説します。 デザイナーでない場合は、色や正確な配置に関して完璧または最終的な UI である必要はありません。コンポーネントの構造を考えることがより重要です。 理想的には、 に従って、コンポーネントが 1 つのことだけを担当する必要があります。 単一責任の原則 下の画像で、紫色の名前はこれから構築するコンポーネントです。それ以外はすべてネイティブ HTML 要素です。お互いの中にいるということは、親子関係が成立する可能性が高いということです。 小道具: 静的バージョンの構築 スケッチができたら、アプリの静的バージョンの構築を開始できます。つまり、UI 要素だけで、インタラクティブ性はまだありません。この部分は非常に簡単で、コツをつかめば、多くのタイピングとほとんど考える必要がありません。 静的バージョンのコードは、この のブランチ「static-version」にあります。完全に機能するアプリのコードはメイン ブランチです。 GitHub リポジトリ 容器 上で概説したように、アプリのすべてのセクションで再利用されるコンテナを作成します。このコンテナは、さまざまな要素を構成する方法の 1 つを示しています。それらを子として渡すことです。 // src/components/Container.tsx const Container = ({ children, title, }: { children: JSX.Element | JSX.Element[]; title?: string; }) => { return ( <div className="bg-green-600 p-4 border shadow rounded-md"> {title && <h2 className="text-xl pb-2 text-white">{title}</h2>} <div>{children}</div> </div> ); }; export default Container; タイプの パラメータを持つ props オブジェクトを取ります。 .これは、他の HTML 要素や作成した他のコンポーネントと一緒に構成できることを意味します。コンテナ内のどこにでもレンダリングできます — この場合は 2 番目の div 内です。 JSX.Element | JSX.Element[] children JSX.Element | JSX.Element[] このアプリでは、App コンポーネント内で使用するときに各セクション (以下で定義) をレンダリングします。 Container は、オプションで という名前の prop も取ります。これは、h2 が存在する場合は常に h2 内でレンダリングされます。 title string // src/App.tsx import Container from "./components/Container"; import Input from "./components/Input"; import Summary from "./components/Summary/Summary"; import Tasks from "./components/Tasks/Tasks"; function App() { return ( <div className="flex justify-center m-5"> <div className="flex flex-col items-center"> <div className="sm:w-[640px] border shadow p-10 flex flex-col gap-10"> <Container title={"Summary"}> <Summary /> </Container> <Container> <Input /> </Container> <Container title={"Tasks"}> <Tasks /> </Container> </div> </div> </div> ); } export default App; まとめ 最初のセクションは、タスクの総数、保留中のタスクの数、および完了したタスクの数の 3 つの項目 (SummaryItem) を示す要約 (Summary コンポーネント) です。これは、コンポーネントを構成する別の方法です。別のコンポーネントの return ステートメントで使用するだけです。 (ただし、不要な再レンダリングやバグにつながる可能性があるため、別のコンポーネント内でコンポーネントを しないことが重要です。) 定義 今のところ、2 つのコンポーネントで静的データのみを使用できます。 // src/components/Summary/SummaryItem.tsx const SummaryItem = ({ itemName, itemValue, }: { itemName: string; itemValue: number; }) => { return ( <article className="bg-green-50 w-36 rounded-sm flex justify-between p-2"> <h3 className="font-bold">{itemName}</h3> <span className="bg-green-900 text-white px-2 rounded-sm"> {itemValue} </span> </article> ); }; export default SummaryItem; // src/components/Summary/Summary.tsx import SummaryItem from "./SummaryItem"; const Summary = () => { return ( <> <div className="flex justify-between"> <SummaryItem itemName={"Total"} itemValue={3} /> <SummaryItem itemName={"To do"} itemValue={2} /> <SummaryItem itemName={"Done"} itemValue={1} /> </div> </> ); }; export default Summary; SummaryItem は、string 型の と 型の の 2 つの props を取ることに気付くでしょう。これらの props は、Summary コンポーネント内で SummaryItem コンポーネントが使用されるときに渡され、SummaryItem JSX でレンダリングされます。 itemName number itemValue タスク 同様に、タスク セクション (最後のセクション) には、TaskItem コンポーネントをレンダリングする Tasks コンポーネントがあります。 今のところ静的データもあります。後で と を props として TaskItem コンポーネントに渡して、再利用可能で動的にする必要があります。 タスク名 ステータス // src/components/Tasks/TaskItem.tsx const TaskItem = () => { return ( <div className="flex justify-between bg-white p-1 px-3 rounded-sm"> <div className="flex gap-2 items-center"> <input type="checkbox" /> Task name </div> <button className="bg-green-200 hover:bg-green-300 rounded-lg p-1 px-3"> Delete </button> </div> ); }; export default TaskItem; // src/components/Tasks/Tasks.tsx import TaskItem from "./TaskItem"; const Tasks = () => { return ( <div className="flex flex-col gap-2"> <TaskItem /> </div> ); }; export default Tasks; 入力 最後に、入力コンポーネントは、ラベル、テキスト型の入力、「タスクを追加」ボタンを備えたフォームです。今のところ何もしませんが、すぐに変更します。 // src/components/Input.tsx const InputContainer = () => { return ( <form action="" className="flex flex-col gap-4"> <div className="flex flex-col"> <label className="text-white">Enter your next task:</label> <input className="p-1 rounded-sm" /> </div> <button type="button" className="bg-green-100 rounded-lg hover:bg-green-200 p-1" > Add task </button> </form> ); }; export default InputContainer; 状態: インタラクティブ性の追加 React にインタラクティブ性を追加するには、コンポーネントの状態に情報を保存する必要があります。 しかし、それを行う前に、時間の経過とともにデータをどのように変化させたいかを考える必要があります。このデータ を特定し、この状態を保存するため を使用する必要があるかを特定する必要があります。 の最小限の表現 にどのコンポーネント 状態の最小限の表現 状態には、アプリをインタラクティブにするために必要なすべての情報が含まれている必要がありますが、それ以上のものは含まれていません。別の値から値を計算できる場合は、そのうちの 1 つだけを状態に保つ必要があります。これにより、コードが冗長になるだけでなく、矛盾する状態値に関連するバグが発生しにくくなります。 この例では、合計タスク、保留中のタスク、および完了したタスクの値を追跡する必要があると考えるかもしれません。 ただし、タスクを追跡するには、各タスクとそのステータス (保留中または完了) を表すオブジェクトを含む 1 つの配列があれば十分です。 const tasks = [ { name: "task one", done: false, }, { name: "task two", done: true, }, ]; このデータを使用すると、配列メソッドを使用して、レンダリング時に必要な他のすべての情報をいつでも見つけることができます。また、矛盾の可能性も回避します。たとえば、合計 4 つのタスクがあるのに、保留中のタスクが 1 つと完了済みのタスクが 1 つしかないなどです。 インタラクティブにするために、フォーム (入力コンポーネント内) にも状態が必要です。 州が住むべき場所 次のように考えてみてください。状態に保存するデータにアクセスする必要があるのはどのコンポーネントですか?単一のコンポーネントの場合、状態はこのコンポーネント自体に存在できます。データが必要なコンポーネントが複数ある場合は、これらのコンポーネントの共通の親を見つける必要があります。 この例では、入力コンポーネントを制御するために必要な状態は、そこでのみアクセスする必要があるため、このコンポーネントに対してローカルにすることができます。 // src/components/Input.tsx import { useState } from "react"; const InputContainer = () => { const [newTask, setNewTask] = useState(""); // Initialize newTask and setNewTask return ( <form action="" className="flex flex-col gap-4"> <div className="flex flex-col"> <label className="text-white">Enter your next task:</label> <input className="p-1 rounded-sm" type="text" value={newTask} // Set the input value to newTask onChange={(e) => setNewTask(e.target.value)} // Set newTask to the input value whenever the user types something /> </div> <button type="submit" className="bg-green-100 rounded-lg hover:bg-green-200 p-1" > Add task </button> </form> ); }; export default InputContainer; これは、入力に 値を表示し、入力が変化するたびに (つまり、ユーザーが何かを入力したときに) 関数を呼び出すことを行っています。 newTask setNewTask UI にすぐに変化が見られるわけではありませんが、これは、入力を制御し、後で使用するためにその値にアクセスできるようにするために必要です。 ただし、タスクを追跡する状態は、SummaryItem コンポーネント (合計、保留中、および完了したタスクの数を表示する必要があります) および TaskItem コンポーネント (必要なタスク) でアクセスする必要があるため、別の方法で処理する必要があります。タスク自体を表示します)。この情報は常に同期している必要があるため、同じ状態である必要があります。 コンポーネント ツリーを見てみましょう (これには を使用できます)。 React 開発ツール 最初の共通の親コンポーネントは App であることがわかります。これが、タスクの状態が存在する場所です。 状態が整ったら、データを使用する必要があるコンポーネントにデータを props として渡すだけです。 (親状態に変更を加えて永続化する方法についてはまだ心配していません。それは次の部分です。) // src/App.tsx import { useState } from "react"; import { v4 as uuidv4 } from "uuid"; import Container from "./components/Container"; import Input from "./components/Input"; import Summary from "./components/Summary/Summary"; import Tasks from "./components/Tasks/Tasks"; export interface Task { name: string; done: boolean; id: string; } const initialTasks = [ { name: "task one", done: false, id: uuidv4(), }, { name: "task two", done: true, id: uuidv4(), }, ]; function App() { const [tasks, setTasks] = useState<Task[]>(initialTasks); return ( <div className="flex justify-center m-5"> <div className="flex flex-col items-center"> <div className="border shadow p-10 flex flex-col gap-10 sm:w-[640px]"> <Container title={"Summary"}> <Summary tasks={tasks} /> </Container> <Container> <Input /> </Container> <Container title={"Tasks"}> <Tasks tasks={tasks} /> </Container> </div> </div> </div> ); } export default App; ここでは、タスクの値をダミー データ ( ) で初期化しています。これは、アプリが終了する前に視覚化できるようにするためです。後で空の配列に変更できるため、新しいユーザーがアプリを新たに開いたときにタスクが表示されません。 initialTasks プロパティと プロパティに加えて、id もタスク オブジェクトに追加します。これはすぐに必要になります。 name done タスク オブジェクトの値の型を使用して を定義し、それを 関数に渡します。 の初期値を空の配列に変更したり、props として渡したりすると、TypeScript はそれを推測できないため、この場合はこれが必要です。 interface useState tasks 最後に、タスクを小道具として Summary および Tasks コンポーネントに渡していることに注意してください。これらのコンポーネントは、それに対応するために変更する必要があります。 // src/components/Summary/Summary.tsx import { Task } from "../../App"; import SummaryItem from "./SummaryItem"; const Summary = ({ tasks }: { tasks: Task[] }) => { const total = tasks.length; const pending = tasks.filter((t) => t.done === false).length; const done = tasks.filter((t) => t.done === true).length; return ( <> <div className="flex flex-col gap-1 sm:flex-row sm:justify-between"> <SummaryItem itemName={"Total"} itemValue={total} /> <SummaryItem itemName={"To do"} itemValue={pending} /> <SummaryItem itemName={"Done"} itemValue={done} /> </div> </> ); }; export default Summary; Summary コンポーネントを更新して、 を小道具として受け入れるようになりました。また、値 、 、 も定義しました。これらは、以前の静的な の代わりに、SummaryItem コンポーネントに props として渡されます。 tasks total pending done itemValue // src/components/Tasks/Tasks.tsx import { Task } from "../../App"; import TaskItem from "./TaskItem"; const Tasks = ({ tasks }: { tasks: Task[] }) => { return ( <div className="flex flex-col gap-2"> {tasks.map((t) => ( <TaskItem key={t.id} name={t.name} /> ))} </div> ); }; export default Tasks; // src/components/Tasks/TaskItem.tsx import { useState } from "react"; const TaskItem = ({ name }: { name: string }) => { const [done, setDone] = useState(false); return ( <div className="flex justify-between bg-white p-1 px-3 rounded-sm gap-4"> <div className="flex gap-2 items-center"> <input type="checkbox" checked={done} onChange={() => setDone(!done)} /> {name} </div> <button className="bg-green-200 hover:bg-green-300 rounded-lg p-1 px-3"> Delete </button> </div> ); }; export default TaskItem; Tasks コンポーネントの場合、 もプロップとして取り、その プロパティを TaskItem コンポーネントにマップします。その結果、 配列内の各オブジェクトの TaskItem コンポーネントを取得します。また、 を小道具として受け入れるように TaskItem コンポーネントを更新します。 task name tasks name 子コンポーネントのリストを取得するたびに一意のキーを渡す必要があるため、ここで id が役に立ちます。キーを追加しない があります。 (本番アプリでは、ID はバックエンドから取得される可能性が最も高いでしょう。) と、 rerender でバグが発生する可能性 今のところの結果は次のとおりです。 サマリー番号とダミー データを反映したタスク名が既に表示されています。しかし、タスクを追加または削除する方法がまだありません。 逆データ フローの追加 アプリを完成させるには、App コンポーネントの状態 (タスク データがある場所) を Input および TaskItem 子コンポーネントから変更する方法が必要です。 そのために、 フックによって生成された関数を使用してイベント ハンドラーを定義し、それらを props として渡します。これを行ったら、子コンポーネントからの適切なユーザー操作中にそれらを呼び出すだけです。 useState バグの原因となるため、 ください。状態オブジェクトを更新するときは、常に新しい状態オブジェクトに置き換えてください。 状態を更新するときは常に状態を変更しないで 以下は、ハンドラーが宣言され、小道具として Input および Tasks コンポーネントに渡された最終的な App コンポーネントです。 は、古いタスクと新しいタスクを含む新しい配列を返します。 は、指定された に対して、反対の プロパティを持つ新しい配列を返します。 は、指定された のタスクを含まない新しい配列を返します。 handleSubmit toggleDoneTask id done handleDeleteTask id // src/App.tsx import { FormEvent, useState } from "react"; import { v4 as uuidv4 } from "uuid"; import Container from "./components/Container"; import Input from "./components/Input"; import Summary from "./components/Summary/Summary"; import Tasks from "./components/Tasks/Tasks"; export interface Task { name: string; done: boolean; id: string; } function App() { const [tasks, setTasks] = useState<Task[]>([]); const handleSubmit = (e: FormEvent<HTMLFormElement>, value: string) => { e.preventDefault(); const newTask = { name: value, done: false, id: uuidv4(), }; setTasks((tasks) => [...tasks, newTask]); }; const toggleDoneTask = (id: string, done: boolean) => { setTasks((tasks) => tasks.map((t) => { if (t.id === id) { t.done = done; } return t; }) ); }; const handleDeleteTask = (id: string) => { setTasks((tasks) => tasks.filter((t) => t.id !== id)); }; return ( <div className="flex justify-center m-5"> <div className="flex flex-col items-center"> <div className="border shadow p-10 flex flex-col gap-10 sm:w-[640px]"> <Container title={"Summary"}> <Summary tasks={tasks} /> </Container> <Container> <Input handleSubmit={handleSubmit} /> </Container> <Container title={"Tasks"}> <Tasks tasks={tasks} toggleDone={toggleDoneTask} handleDelete={handleDeleteTask} /> </Container> </div> </div> </div> ); } export default App; これは、 を使用して App コンポーネントの状態を更新する最終的な入力コンポーネントです。 handleSubmit // src/components/Input.tsx import { FormEvent, useState } from "react"; const InputContainer = ({ handleSubmit, }: { handleSubmit: (e: FormEvent<HTMLFormElement>, value: string) => void; }) => { const [newTaskName, setNewTaskName] = useState(""); return ( <form action="" className="flex flex-col gap-4" onSubmit={(e) => { handleSubmit(e, newTaskName); setNewTaskName(""); }} > <div className="flex flex-col"> <label className="text-white">Enter your next task:</label> <input className="p-1 rounded-sm" type="text" value={newTaskName} onChange={(e) => setNewTaskName(e.target.value)} /> </div> <button type="submit" className="bg-green-100 rounded-lg hover:bg-green-200 p-1" > Add task </button> </form> ); }; export default InputContainer; これは、App から TaskItem に props を渡すように更新した最終的な Tasks コンポーネントです。また、「まだタスクがありません!」を返す三項演算子も追加しました。タスクがないとき。 // src/components/Tasks/Tasks.tsx import { Task } from "../../App"; import TaskItem from "./TaskItem"; const Tasks = ({ tasks, toggleDone, handleDelete, }: { tasks: Task[]; toggleDone: (id: string, done: boolean) => void; handleDelete: (id: string) => void; }) => { return ( <div className="flex flex-col gap-2"> {tasks.length ? ( tasks.map((t) => ( <TaskItem key={t.id} name={t.name} done={t.done} id={t.id} toggleDone={toggleDone} handleDelete={handleDelete} /> )) ) : ( <span className="text-green-100">No tasks yet!</span> )} </div> ); }; export default Tasks; これが最後の TaskItem コンポーネントで、 と を使用して App コンポーネントの状態を更新します。 toggleDone handleDelete // src/components/Tasks/TaskItem.tsx const TaskItem = ({ name, done, id, toggleDone, handleDelete, }: { name: string; done: boolean; id: string; toggleDone: (id: string, done: boolean) => void; handleDelete: (id: string) => void; }) => { return ( <div className="flex justify-between bg-white p-1 px-3 rounded-sm gap-4"> <div className="flex gap-2 items-center"> <input type="checkbox" checked={done} onChange={() => toggleDone(id, !done)} /> {name} </div> <button className="bg-green-200 hover:bg-green-300 rounded-lg p-1 px-3" type="button" onClick={() => handleDelete(id)} > Delete </button> </div> ); }; export default TaskItem; いくつかのタスクを追加した後の最終的なアプリは次のとおりです。 一緒にコーディングしている場合は、次 独自のアプリをデプロイできます。 の手順に従って ここで実行したすべてのコードを含むリポジトリと、 でアプリのライブ バージョンを見つけることができ 。 ここ ます 最後の言葉 結論として、to-do リスト アプリを作成することは、React とその原則について学び、理解を深めるための優れた方法です。プロセスを小さなステップに分割し、ベスト プラクティスに従うことで、機能的なアプリを比較的短時間で作成できます。 以下について説明しました。 コンポーネント、状態、および逆データ フローの主要な概念。 アプリの設計とアーキテクチャ。 単一責任原則などのベスト プラクティス このガイドで概説されている手順に従うことで、シンプルな React アプリを構築し、それを自分のプロジェクトに適用する方法をしっかりと理解できるはずです。 ハッピーコーディング!