Reactのスプレッド構文とは?...の使い方を理解する

Reactでスプレッド構文(...)を使う方法を詳しく解説。配列やオブジェクトの展開、propsの渡し方、stateの更新テクニックまで、実践的な使用例とともに初心者向けに説明します。

Learning Next 運営
24 分で読めます

React で「...」という記号を見たことはありませんか?

「配列やオブジェクトの操作で便利だと聞いたけど、具体的な使い方が分からない」 「propsを渡すときに使うと良いらしいけど、どんな場面で使うの?」

こんなふうに思ったことはありませんか?

この記事では、Reactにおけるスプレッド構文の使い方を基本から応用まで詳しく解説します。 実践的な例とともに、効率的なコードを書くためのテクニックを一緒に学んでいきましょう。

スプレッド構文って何?

スプレッド構文(...)は、配列やオブジェクトを展開するためのJavaScriptの記法です。 簡単に言うと、データを「バラバラに分解」して使える便利な機能なんです。

基本的な概念

スプレッド構文を使うと、配列やオブジェクトの要素を個別に展開できます。

// 配列の展開
const numbers = [1, 2, 3];
const expandedNumbers = [...numbers];
console.log(expandedNumbers); // [1, 2, 3]
// オブジェクトの展開
const user = { name: '田中', age: 25 };
const expandedUser = { ...user };
console.log(expandedUser); // { name: '田中', age: 25 }

この例では、元のnumbers配列とuserオブジェクトを展開して、新しいデータを作成しています。 元のデータを変更せずに新しいデータを作成できるのが特徴です。

なぜReactで重要なの?

Reactでは、以下の理由でスプレッド構文がとても重要です。

  • イミュータブルな更新が可能になる
  • Propsの効率的な受け渡しができる
  • 配列やオブジェクトの結合が簡単になる
  • Stateの安全な更新ができる

これらの機能により、より安全で読みやすいコードを書けるようになります。 特に、Reactでは元のデータを変更せずに新しいデータを作ることが重要なので、スプレッド構文はとても便利なツールなんです。

配列でのスプレッド構文

配列操作でのスプレッド構文の使い方を確認しましょう。

配列の結合

複数の配列を組み合わせる時に、スプレッド構文が活躍します。

// 配列の結合例
function TodoList() {
const completedTodos = [
{ id: 1, text: '買い物', completed: true },
{ id: 2, text: '掃除', completed: true }
];
const pendingTodos = [
{ id: 3, text: '洗濯', completed: false },
{ id: 4, text: '料理', completed: false }
];
// スプレッド構文で配列を結合
const allTodos = [...completedTodos, ...pendingTodos];
return (
<ul>
{allTodos.map(todo => (
<li key={todo.id}>
{todo.text} - {todo.completed ? '完了' : '未完了'}
</li>
))}
</ul>
);
}

この例では、完了したタスクと未完了のタスクの配列を結合しています。 従来のconcat()メソッドよりも短く、読みやすいコードになりますね。

配列への要素追加

既存の配列に新しい要素を追加する時も、スプレッド構文が便利です。

// 配列への要素追加例
function ShoppingCart() {
const [items, setItems] = useState([
{ id: 1, name: 'りんご', price: 100 },
{ id: 2, name: 'バナナ', price: 150 }
]);
const addItem = (newItem) => {
// 先頭に追加
setItems([newItem, ...items]);
// 末尾に追加する場合はこちら
// setItems([...items, newItem]);
};
const handleAddApple = () => {
const newApple = { id: Date.now(), name: 'りんご', price: 100 };
addItem(newApple);
};
return (
<div>
<button onClick={handleAddApple}>りんご追加</button>
<ul>
{items.map(item => (
<li key={item.id}>{item.name} - ¥{item.price}</li>
))}
</ul>
</div>
);
}

setItems([newItem, ...items])の部分で、新しいアイテムを先頭に追加しています。 この方法により、元の配列を変更せずに新しい配列を作成できます。

配列の一部を削除

配列から特定の要素を削除する時も、スプレッド構文と組み合わせて使用できます。

// 配列の一部を削除する例
function TaskManager() {
const [tasks, setTasks] = useState([
{ id: 1, title: 'タスク1', completed: false },
{ id: 2, title: 'タスク2', completed: true },
{ id: 3, title: 'タスク3', completed: false }
]);
const removeTask = (taskId) => {
// 指定したID以外の要素を保持
setTasks(tasks.filter(task => task.id !== taskId));
};
const removeCompletedTasks = () => {
// 完了していないタスクのみを保持
setTasks(tasks.filter(task => !task.completed));
};
return (
<div>
<button onClick={removeCompletedTasks}>
完了タスクを削除
</button>
<ul>
{tasks.map(task => (
<li key={task.id}>
{task.title}
<button onClick={() => removeTask(task.id)}>
削除
</button>
</li>
))}
</ul>
</div>
);
}

この例では、filterメソッドを使って条件に合う要素だけを残しています。 配列の削除もイミュータブルな方法で実装できますね。

オブジェクトでのスプレッド構文

オブジェクト操作でのスプレッド構文の使い方を確認しましょう。

オブジェクトの結合

複数のオブジェクトを組み合わせる時に、スプレッド構文が活用できます。

// オブジェクトの結合例
function UserProfile() {
const [user, setUser] = useState({
name: '山田太郎',
age: 30
});
const [settings, setSettings] = useState({
theme: 'dark',
notifications: true
});
// オブジェクトを結合
const userWithSettings = { ...user, ...settings };
return (
<div>
<h2>{userWithSettings.name}</h2>
<p>年齢: {userWithSettings.age}</p>
<p>テーマ: {userWithSettings.theme}</p>
<p>通知: {userWithSettings.notifications ? 'ON' : 'OFF'}</p>
</div>
);
}

この例では、ユーザー情報と設定情報を結合して一つのオブジェクトにしています。 複数のオブジェクトを簡単に結合できて便利ですね。

オブジェクトの一部更新

既存のオブジェクトの一部だけを更新したい時にも、スプレッド構文が重宝します。

// オブジェクトの一部更新例
function EditableProfile() {
const [profile, setProfile] = useState({
name: '佐藤花子',
age: 25,
email: 'sato@example.com',
city: '東京'
});
const updateProfile = (field, value) => {
// 既存のプロパティを保持しながら、特定のフィールドを更新
setProfile(prevProfile => ({
...prevProfile,
[field]: value
}));
};
return (
<div>
<h2>{profile.name}</h2>
<input
value={profile.name}
onChange={(e) => updateProfile('name', e.target.value)}
placeholder="名前"
/>
<input
value={profile.email}
onChange={(e) => updateProfile('email', e.target.value)}
placeholder="メールアドレス"
/>
<input
value={profile.city}
onChange={(e) => updateProfile('city', e.target.value)}
placeholder="都市"
/>
</div>
);
}

{...prevProfile, [field]: value}の部分で、既存のプロパティを保持しながら特定のフィールドだけを更新しています。 この方法により、オブジェクトの特定のプロパティのみを安全に更新できます。

ネストしたオブジェクトの更新

オブジェクトの中にオブジェクトがある場合(ネストしたオブジェクト)の更新方法も見てみましょう。

// ネストしたオブジェクトの更新例
function NestedObjectUpdate() {
const [data, setData] = useState({
user: {
personal: {
name: '田中一郎',
age: 35
},
contact: {
email: 'tanaka@example.com',
phone: '090-1234-5678'
}
},
settings: {
theme: 'light',
language: 'ja'
}
});
const updateUserName = (newName) => {
setData(prevData => ({
...prevData,
user: {
...prevData.user,
personal: {
...prevData.user.personal,
name: newName
}
}
}));
};
return (
<div>
<h2>{data.user.personal.name}</h2>
<input
value={data.user.personal.name}
onChange={(e) => updateUserName(e.target.value)}
placeholder="名前を変更"
/>
</div>
);
}

ネストしたオブジェクトでは、各レベルでスプレッド構文を使って展開する必要があります。 少し複雑に見えますが、この方法で安全にデータを更新できますよ。

Propsでのスプレッド構文

コンポーネント間でのProps受け渡しでも、スプレッド構文が大活躍します。

Propsの一括受け渡し

たくさんのpropsを一度に渡したい時に、スプレッド構文が便利です。

// Props の一括受け渡し例
function UserCard({ name, age, email, city }) {
return (
<div className="user-card">
<h3>{name}</h3>
<p>年齢: {age}</p>
<p>メール: {email}</p>
<p>都市: {city}</p>
</div>
);
}
function UserList() {
const users = [
{ id: 1, name: '田中太郎', age: 30, email: 'tanaka@example.com', city: '東京' },
{ id: 2, name: '山田花子', age: 25, email: 'yamada@example.com', city: '大阪' }
];
return (
<div>
{users.map(user => (
<UserCard
key={user.id}
{...user} // スプレッド構文でpropsを一括受け渡し
/>
))}
</div>
);
}

{...user}の部分で、userオブジェクトのすべてのプロパティを一度にpropsとして渡しています。 name、age、email、cityを個別に書く必要がなくて、とても効率的ですね。

Propsの選択的受け渡し

必要なpropsだけを取り出して、残りを子コンポーネントに渡すパターンも覚えておきましょう。

// Props の選択的受け渡し例
function BaseButton({ children, onClick, ...otherProps }) {
return (
<button
onClick={onClick}
{...otherProps} // 残りのpropsを展開
>
{children}
</button>
);
}
function CustomButton() {
const handleClick = () => {
console.log('ボタンがクリックされました');
};
return (
<BaseButton
onClick={handleClick}
className="custom-button"
disabled={false}
type="button"
>
カスタムボタン
</BaseButton>
);
}

{ children, onClick, ...otherProps }の部分で、childrenとonClickを取り出し、残りのpropsをotherPropsにまとめています。 そして{...otherProps}で、残りのpropsを子のbuttonタグに渡しています。

実践的な使用例

実際の開発でよく使われるスプレッド構文のパターンを確認しましょう。

フォームの状態管理

フォームの入力値を管理する時の実用的な例を見てみましょう。

// フォームの状態管理例
function ContactForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
message: '',
agreeToTerms: false
});
const handleInputChange = (e) => {
const { name, value, type, checked } = e.target;
setFormData(prevData => ({
...prevData,
[name]: type === 'checkbox' ? checked : value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('フォームデータ:', formData);
};
return (
<form onSubmit={handleSubmit}>
<input
name="name"
value={formData.name}
onChange={handleInputChange}
placeholder="名前"
/>
<input
name="email"
value={formData.email}
onChange={handleInputChange}
placeholder="メールアドレス"
/>
<textarea
name="message"
value={formData.message}
onChange={handleInputChange}
placeholder="メッセージ"
/>
<label>
<input
name="agreeToTerms"
type="checkbox"
checked={formData.agreeToTerms}
onChange={handleInputChange}
/>
利用規約に同意する
</label>
<button type="submit">送信</button>
</form>
);
}

この例では、handleInputChange関数で、どの入力欄が変更されても対応できるように実装しています。 [name]: type === 'checkbox' ? checked : valueの部分で、入力の種類に応じて適切な値を設定しています。

配列の動的更新

リストアイテムの追加や更新を行う実用的な例も見てみましょう。

// 配列の動的更新例
function DynamicList() {
const [items, setItems] = useState([
{ id: 1, name: 'アイテム1', completed: false },
{ id: 2, name: 'アイテム2', completed: true }
]);
const toggleItem = (id) => {
setItems(prevItems =>
prevItems.map(item =>
item.id === id
? { ...item, completed: !item.completed }
: item
)
);
};
const addItem = () => {
const newItem = {
id: Date.now(),
name: `アイテム${items.length + 1}`,
completed: false
};
setItems(prevItems => [...prevItems, newItem]);
};
return (
<div>
<button onClick={addItem}>アイテム追加</button>
<ul>
{items.map(item => (
<li key={item.id}>
<span
onClick={() => toggleItem(item.id)}
style={{
textDecoration: item.completed ? 'line-through' : 'none',
cursor: 'pointer'
}}
>
{item.name}
</span>
</li>
))}
</ul>
</div>
);
}

toggleItem関数では、指定されたIDのアイテムだけを更新し、それ以外はそのまま保持しています。 { ...item, completed: !item.completed }の部分で、既存のプロパティを保持しながらcompletedだけを反転させています。

よくある間違いと注意点

スプレッド構文を使用する際の注意点を確認しましょう。

浅いコピーの制限

スプレッド構文は「浅いコピー」しか作成しないため、ネストしたオブジェクトでは注意が必要です。

// ❌ 間違った例:ネストしたオブジェクトで問題が発生
function ProblematicUpdate() {
const [data, setData] = useState({
user: {
name: '田中',
preferences: {
theme: 'dark',
notifications: true
}
}
});
const updateTheme = (newTheme) => {
// これは浅いコピーのため、preferences オブジェクトは共有される
const newData = { ...data };
newData.user.preferences.theme = newTheme; // 元のデータも変更される
setData(newData);
};
return <div>{data.user.preferences.theme}</div>;
}
// ✅ 正しい例:深いコピーを実装
function CorrectUpdate() {
const [data, setData] = useState({
user: {
name: '田中',
preferences: {
theme: 'dark',
notifications: true
}
}
});
const updateTheme = (newTheme) => {
setData(prevData => ({
...prevData,
user: {
...prevData.user,
preferences: {
...prevData.user.preferences,
theme: newTheme
}
}
}));
};
return <div>{data.user.preferences.theme}</div>;
}

間違った例では、newData.user.preferences.theme = newThemeで直接プロパティを変更しています。 これだと元のデータも変更されてしまうので、Reactが変更を検知できない場合があります。

正しい例では、各レベルでスプレッド構文を使用して、すべてのオブジェクトを新しく作成しています。

パフォーマンスの考慮

スプレッド構文を使いすぎると、パフォーマンスに影響することがあります。

// ❌ 不要な再レンダリングを引き起こす例
function InefficientComponent({ items }) {
return (
<div>
{/* 毎回新しい配列を作成 */}
{[...items, { id: 'temp', name: 'テンポラリ' }].map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}
// ✅ 効率的な実装
function EfficientComponent({ items }) {
const itemsWithTemp = useMemo(() => [
...items,
{ id: 'temp', name: 'テンポラリ' }
], [items]);
return (
<div>
{itemsWithTemp.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}

間違った例では、コンポーネントがレンダリングされる度に新しい配列を作成しています。 正しい例では、useMemoを使って、itemsが変更された時だけ新しい配列を作成するようにしています。

まとめ

Reactにおけるスプレッド構文の重要なポイントをまとめます。

基本的な使い方

  • 配列やオブジェクトの展開に使用する
  • イミュータブルな更新が可能になる
  • Propsの効率的な受け渡しができる

注意すべきポイント

  • ネストしたデータの更新には注意が必要
  • パフォーマンスを考慮した使用が大切
  • 浅いコピーの制限を理解する

スプレッド構文を適切に使用することで、より安全で読みやすいReactコードを書くことができます。 最初は慣れないかもしれませんが、実際に使いながら身につけていきましょう。

ぜひ実際のプロジェクトでスプレッド構文を活用してみてくださいね!

関連記事