スプレッド構文をなぜ使うのか
理解のためにはまず破壊的メソッドと非破壊的メソッドを知る必要がある。
- 破壊的メソッドを見ていく
popやpushのような破壊的なメソッドを使うことは最近のJavaScriptではNGとなっている。
破壊的メソッドを避けるためにスプレッド構文が使われると覚えておく。
一般的な破壊的メソッド
Array.prototype.push(): 配列の末尾に要素を追加します。
Array.prototype.pop(): 配列の末尾から要素を削除します。
Array.prototype.shift(): 配列の先頭から要素を削除して、残りの要素を前に詰めます。
Array.prototype.unshift(): 配列の先頭に要素を追加し、他の要素を後ろにずらします。
Array.prototype.sort(): 配列の要素をソートします。デフォルトでは破壊的なソートですが、比較関数を提供することで非破壊的なソートも可能です。
Array.prototype.reverse(): 配列の要素の順序を逆転させます。
Array.prototype.splice(): 配列内の要素を削除または置換し、新しい要素を追加します。
破壊的メソッドがなぜだめなのかを理解するためには
ミュータブル / イミュータブルという概念を理解する必要がある。ミュータブル / イミュータブルというのは一度値を作成した後に その値を変更できるかどうか。- ミュータブルは 一度作成した後に変更できる
- イミュータブルは 一度作成した後に変更できない
JavaScriptにおいては
- 文字列や数値はデフォルトでイミュータブルになっている。
- 配列とオブジェクトはデフォルトでミュータブルになっている。(値を作成した後も変更できてしまう)
このミュータブルというのが最近のJavaScriptの世界では悪とされているのでミュータブルを避けてイミュータブルにしていこうという動きがある。ただし配列とオブジェクトはミュータブルなので適切に扱う必要があるんですが、破壊的メソッドを使ったらミュータブルなものとして扱うことになるので、破壊的メソッドを避けてスプレッド構文を用いて配列やオブジェクトもイミュータブルに扱いたいというのが最近のJavaScriptです。この流れをしっかり覚える。
// 数値の場合
import Head from 'next/head'
import { Main } from '../components/Main/Main'
import styles from '@/styles/Home.module.css'
import { Header } from '@/components/header/Header'
import { Links } from '@/components/links/Links'
import { useCallback, useEffect, useState } from 'react'
export default function Home() {
const [count, setCount] = useState(1);
const [text, setText] = useState("");
const [isShow, setIsShow] = useState(true);
const [array, setArray] = useState([]);
const handleClick = useCallback((e) => {
if (count < 10 ) {
setCount(prevCount => prevCount + 1);
}
},[count]);
const handleChange = useCallback((e) => {
if (e.target.value.length > 5) {
alert("5文字以内にしてください")
return;
}
setText(e.target.value.trim())
}, [])
// useCallbackにすることで何度も生成されるのを防ぐ
const handleDisplay = useCallback(() => {
setIsShow((prevIsShow) => !prevIsShow)
}, [])
const handleAdd = useCallback(() => {
setArray((prevArray) => {
// ミュータブル
// const newArray = prevArray;
// newArray.push(1);
// console.log(newArray === prevArray);
// 配列は増えているが再レンダリングはされない
// イミュータブル
const newArray = [...prevArray, 1]
//スプレッド構文を用いることで
// 新しい配列が生成され再レンダリングされる
return newArray;
})
}, [])
useEffect(() => {
document.body.style.backgroundColor = "lightblue"
return () => {
document.body.style.backgroundColor = ""
}
}, [count])
return (
<div className={styles.container}>
<Head>
<title>index</title>
</Head>
<Header />
{isShow ? <h1>{count}</h1> : null}
<button onClick={handleClick}>ボタン</button>
<button onClick={handleDisplay}>{isShow ? "非表示" : "表示"}</button>
<input
type="text"
value={text}
onChange={handleChange}
/>
<button onClick={handleAdd}>追加</button>
<ul>
{array.map(item => {
return (
<li key={item}>{item}</li>
)
})}
</ul>
<Main page="index" />
<Links />
</div>
)
}
// 文字列入力の場合
import Head from 'next/head'
import { Main } from '../components/Main/Main'
import styles from '@/styles/Home.module.css'
import { Header } from '@/components/header/Header'
import { Links } from '@/components/links/Links'
import { useCallback, useEffect, useState } from 'react'
export default function Home() {
const [count, setCount] = useState(1);
const [text, setText] = useState("");
const [isShow, setIsShow] = useState(true);
const [array, setArray] = useState([]);
const handleClick = useCallback((e) => {
if (count < 10 ) {
setCount(prevCount => prevCount + 1);
}
},[count]);
const handleChange = useCallback((e) => {
if (e.target.value.length > 5) {
alert("5文字以内にしてください")
return;
}
setText(e.target.value.trim())
}, [])
// useCallbackにすることで何度も生成されるのを防ぐ
const handleDisplay = useCallback(() => {
setIsShow((prevIsShow) => !prevIsShow)
}, [])
const handleAdd = useCallback(() => {
setArray((prevArray) => {
if (prevArray.some((item) => item === text)) {
alert("同じ要素がすでに存在します。");
return prevArray;
}
return [...prevArray, text];
})
}, [text]) //[text]がないと関数が再生成されないので
// 空文字がどんどん追加されてしまうので注意
useEffect(() => {
document.body.style.backgroundColor = "lightblue"
return () => {
document.body.style.backgroundColor = ""
}
}, [count])
return (
<div className={styles.container}>
<Head>
<title>index</title>
</Head>
<Header />
{isShow ? <h1>{count}</h1> : null}
<button onClick={handleClick}>ボタン</button>
<button onClick={handleDisplay}>{isShow ? "非表示" : "表示"}</button>
<input
type="text"
value={text}
onChange={handleChange}
/>
<button onClick={handleAdd}>追加</button>
<ul>
{array.map(item => {
return (
<li key={item}>{item}</li>
)
})}
</ul>
<Main page="index" />
<Links />
</div>
)
}
ミュータブルな操作とスプレッド構文を使用したイミュータブルな操作の追加例を示します。
import React, { useState } from 'react';
function MutableExample() {
// ミュータブルな操作の例
const [person, setPerson] = useState({ name: 'John', age: 30 });
const changeName = () => {
// ミュータブルな操作で、直接名前を変更する
person.name = 'Jane';
setPerson(person);
};
return (
<div>
<h2>ミュータブルな操作</h2>
<p>Name: {person.name}, Age: {person.age}</p>
<button onClick={changeName}>名前を変更</button>
</div>
);
}
// 直接オブジェクトのプロパティを変更してもコンポーネントの再レンダリングは起こりません。
// これは、Reactがコンポーネントの状態の変更を検出するために、
// 新しいオブジェクトを生成する必要があるためです。
// そのため、オブジェクトのプロパティを変更してもコンポーネントは更新されません。
// 正しくは、ミュータブルな変更を避けてイミュータブルな操作を行うべきです。
// function ImmutableExample() {}はしっかり表示が変わる
function ImmutableExample() {
// イミュータブルな操作の例
const [person, setPerson] = useState({ name: 'John', age: 30 });
const changeName = () => {
// スプレッド構文を用いて、新しいオブジェクトを生成し名前を変更する
const newPerson = { ...person, name: 'Jane' };
setPerson(newPerson);
};
return (
<div>
<h2>イミュータブルな操作</h2>
<p>Name: {person.name}, Age: {person.age}</p>
<button onClick={changeName}>名前を変更</button>
</div>
);
}
function App() {
return (
<div>
<MutableExample />
<ImmutableExample />
</div>
);
}
export default App;
このコードでは、ミュータブルな操作とスプレッド構文を使用したイミュータブルな操作の違いを示しています。MutableExampleコンポーネントでは、ミュータブルな操作によって元のオブジェクトが変更されることを示しています。一方、ImmutableExampleコンポーネントでは、スプレッド構文を使用して新しいオブジェクトを生成し、元のオブジェクトを変更しないイミュータブルな操作を行っています。