私は破壊的メソッドへの理解が乏しかったために何度かReactで苦しんだことがあります。
自分の思考整理と同じく苦しんでいる人のために解説していきたいと思います。
#そもそも破壊的メソッドとは?
配列オブジェクトを変更するメソッドで、そのまま使用することは推奨されていないメソッドです。
破壊的メソッドの例を以下に挙げたいと思います。
・push
・pop
・splice
・reverse
・shift
これらが破壊的メソッドの一例になります。
詳しくは下記を見てみてください。
https://qiita.com/Shokorep/items/929e2e66908eaa915286
##なぜ破壊的メソッドがいけないのか。
プログラミングにはミュータブルとイミュータブルという概念があります。
(javascriptにフォーカスを当てて記載しています)
・ミュータブルとは値を設定した後に、その値を変更できること。
例)配列、オブジェクト
・イミュータブルとは値を設定した後に、その値を変更できないこと。
例)文字列、数値
const num = [1, 2, 3]
num.push(4)
console.log(num)
//1,2,3,4
//値が変更されてしまっている。
これらの破壊的メソッドは意図せぬ副作用を与えてしまうことがあり、昨今のJavascriptでは破壊的メソッドは避けて、イミュータブルな書き方をする必要があります。
#ではどうすれば良いのか?
解決策はスプレッド構文を使うことです。
(immerなどのライブラリーを使うというのも手です)
const num = [1, 2, 3]
num.push(4)
//これは破壊的メソッドになるのでNG
const newNum = [...num, 4]
//これは非破壊的メソッドなのでOK
//スプレッド構文を使うことでnumはミュターブルではなくイミュータブルなものとして扱われます。
#Reactでどのようにハマったのか。
useStateの更新関数内で破壊的メソッドを使用して意図した挙動になりませんでした。
どうして破壊的メソッドを使用してはいけないのかを説明する前にReactにおいて再レンダリングされるタイミングを整理しておきたいと思います。
・stateが更新されたコンポーネントは再レンダリング
・propsが変更されたコンポーネントは再レンダリング
・再レンダリングされたコンポーネント配下の子要素は再レンダリング
ではどうして、useStateの更新関数内で破壊的メソッドを使用してはいけないのか。
それは再レンダリングする条件として「stateが更新されたコンポーネントは再レンダリング」が必要、つまりはstateを更新する必要があります。
const [num, setNum] = useState([0])
const addNum = () => {
setNum((preNum) => {
const newNum = preNum
newNum.push(1)
//破壊的メソッド
console.log(newNum)
//ボタンを押すとconsoleでは値が増えていく。
return newNum
})}
return (
<>
<button onClick={addNum}>1増えるよ</button>
<p>{num}</p>
//ここの値はいくらボタンを押しても更新されない
</>
);
上のようにnumの値は増えていきません。
これは、newNumとpreNumの値を比較した時に同じ値だと認識されてしまい、再レンダリングが起きず、numが更新されていないことが原因です。
Reactでもイミュータブルが求められています。
前の値をそのまま変更してもダメで、新しい値を生成して返す必要があるので下記のようにスプレッド構文で書き直す必要があります。
const [num, setNum] = useState([0])
const addNum = () => {
setNum((preNum) => {
const newNum = [...preNum, 1]
//非破壊的メソッド
console.log(newNum)
//ボタンを押すと値が増えていく。
return newNum
})
}
console.log(num)
return (
<>
<button onClick={addNum}>1増えるよ</button>
<p>{num}</p>
//ボタンを押すと値が増えていく。
</>
);
#最後に
破壊的メソッドを使う時には細心の注意を払って使っていきたいと思います。