
React演習問題(JSX・コンポーネント・Props・State)
初級問題(9問) JSXの書き方 <div class="container"> <h1 id="title&q […]
この記事では、React初学者向けのToDoアプリ演習の解答例と、この演習で学ぶ重要なポイントについて詳しく解説します。State管理というReactの核心的な概念を、実際のコードを通じて理解していきましょう。
まずは完成形のコード全体を見てみましょう。src/App.js
の内容です:
import React, { useState } from 'react';
import './App.css';
function App() {
// Stateの宣言
const [todos, setTodos] = useState([]);
const [inputValue, setInputValue] = useState('');
// ToDoを追加する関数
const addTodo = () => {
if (inputValue.trim()) { // 空文字やスペースのみの入力を防ぐ
setTodos([...todos, { text: inputValue, completed: false }]);
setInputValue(''); // 入力欄を空にする
}
};
// ToDoの完了/未完了を切り替える関数
const toggleTodo = (index) => {
const newTodos = [...todos]; // 現在のtodos配列をコピー
newTodos[index].completed = !newTodos[index].completed; // 指定したToDoの状態を反転
setTodos(newTodos); // 更新した配列でStateを更新
};
return (
<div className="app">
<h1>ToDoアプリ</h1>
{/* ToDo追加フォーム */}
<div className="todo-form">
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="新しいToDoを入力"
onKeyPress={(e) => e.key === 'Enter' && addTodo()} // Enterキーでも追加可能
/>
<button onClick={addTodo}>追加</button>
</div>
{/* ToDoリスト表示 */}
<ul className="todo-list">
{todos.map((todo, index) => (
<li
key={index}
onClick={() => toggleTodo(index)}
className={todo.completed ? 'completed' : ''}
>
<span>{todo.text}</span>
<button
className="delete-btn"
onClick={(e) => {
e.stopPropagation(); // 親要素へのイベント伝播を防ぐ
const newTodos = todos.filter((_, i) => i !== index);
setTodos(newTodos);
}}
>
削除
</button>
</li>
))}
</ul>
{/* 完了済みToDoカウンター */}
<div className="counter">
完了: {todos.filter(todo => todo.completed).length} / 全体: {todos.length}
</div>
</div>
);
}
export default App;
const [todos, setTodos] = useState([]);
const [inputValue, setInputValue] = useState('');
useState
はReactのフックの1つで、コンポーネント内で状態を管理するために使用します状態変数
と状態更新関数
を取得しますuseState
の引数には状態の初期値を指定します(ここでは空の配列と空文字)setTodos
やsetInputValue
)を使用しますconst addTodo = () => {
if (inputValue.trim()) {
setTodos([...todos, { text: inputValue, completed: false }]);
setInputValue('');
}
};
inputValue.trim()
で入力値の前後の空白を削除し、空でないことを確認...
)を使って既存のtodosを展開し、新しいToDoを追加text
(内容)とcompleted
(完了状態)のプロパティを持つsetInputValue('')
で入力欄をクリアconst toggleTodo = (index) => {
const newTodos = [...todos]; // 配列をコピー
newTodos[index].completed = !newTodos[index].completed;
setTodos(newTodos);
};
[...todos]
で配列のシャローコピーを作成setTodos
で状態を更新<ul className="todo-list">
{todos.map((todo, index) => (
<li key={index} onClick={() => toggleTodo(index)}>
{/* ... */}
</li>
))}
</ul>
map
メソッドを使って配列をJSX要素に変換key
プロパティが必要(ここではindex
を使用)key
はReactが要素を効率的に更新するために使用されます<input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
onChange
で入力値の変更を検知し、setInputValue
で状態を更新<li className={todo.completed ? 'completed' : ''}>
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.text}
</span>
</li>
解答例には以下の発展的な機能も含めています:
onKeyPress={(e) => e.key === 'Enter' && addTodo()}
onClick={(e) => {
e.stopPropagation();
const newTodos = todos.filter((_, i) => i !== index);
setTodos(newTodos);
}}
filter
メソッドで該当するToDoを除いた新しい配列を作成e.stopPropagation()
で親要素のクリックイベントが発火しないように防止<div className="counter">
完了: {todos.filter(todo => todo.completed).length} / 全体: {todos.length}
</div>
filter
メソッドで完了済みToDoを抽出し、その数を表示src/App.css
の内容例:
.app {
max-width: 500px;
margin: 0 auto;
padding: 20px;
font-family: Arial, sans-serif;
}
.todo-form {
display: flex;
margin-bottom: 20px;
}
.todo-form input {
flex-grow: 1;
padding: 10px;
font-size: 16px;
border: 1px solid #ddd;
border-radius: 4px;
}
.todo-form button {
padding: 10px 15px;
margin-left: 10px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.todo-list {
list-style: none;
padding: 0;
}
.todo-list li {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px;
margin-bottom: 8px;
background-color: #f9f9f9;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
}
.todo-list li:hover {
background-color: #f0f0f0;
}
.todo-list li.completed {
background-color: #e8f5e9;
}
.todo-list li.completed span {
text-decoration: line-through;
color: #888;
}
.delete-btn {
padding: 5px 10px;
background-color: #ff4444;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
}
.delete-btn:hover {
background-color: #cc0000;
}
.counter {
margin-top: 20px;
text-align: right;
color: #666;
font-size: 14px;
}
このToDoアプリを通じて学ぶべきReactの重要な概念:
useState
を使ったデータの保持と更新A: Reactは状態の変更を検知して再レンダリングを行いますが、直接変更すると検知できない場合があります。また、不変性を保つことで予期しない副作用を防ぎ、デバッグが容易になります。
A: 理想的にはユニークで安定したID(データベースのIDなど)を使用します。この例では簡易的に配列のindexを使用していますが、リストが変更される可能性がある場合、indexを使うとパフォーマンス問題やバグの原因になることがあります。
A: スプレッド演算子を使うことで、既存の配列やオブジェクトを浅くコピー(シャローコピー)できます。状態の不変性を保ちつつ、新しい配列/オブジェクトを作成するのに便利です。
このToDoアプリの演習を通じて、Reactの状態管理の基本を実践的に学びました。小さなアプリですが、Reactの核心的な概念が詰まっています。次のステップとして、より複雑な状態管理(useReducerやContext API)や、コンポーネントの分割などに挑戦してみると良いでしょう。