🐷
配列を用いたuseStateの落とし穴
useStateで状態管理ができる。
import { useState } from "react"
export default function Home() {
const [val, setVal] = useState("hello")
function handle() {
setVal("world")
}
return <div onClick={ handle }>{ val }</div> // hello
}
という感じでsetValを使ってvalに変更を加える。
上の例ではvalは文字列だが配列を扱いたい時は
import { useState } from "react"
export default function Home() {
const [arr, setArr] = useState([1, 2, 3])
function handle() {
let tmp = arr.map(a => a)
tmp[2] = 100
setVal(tmp)
}
return (
<>
arr.map(a => <div id={ a }>{ a }</div>)
<div onClick={ handle }></div>
</>
)
return <div onClick={ handle }>{ va }</div> // [1, 2, 3]
}
ここでhandle関数では直接変更せずにtmpに入れていることに注意。
ここがオブジェクトの配列だと以下のような罠にハマる
import { useState } from "react"
export default function Home() {
const [arr, setArr] = useState([{name: "Sato", age: 23}, {name: "Yogo", age: 12})
function handle() {
arr[1].name = "Taro"
console.log(arr) // 値は変更されている
setArr(arr) // 値は変更されない
}
return (
<>
arr.map(a => <div id={ a }>{ a }</div>)
<div onClick={ handle }></div>
</>
)
return <div onClick={ handle }>{ va }</div> // [1, 2, 3]
}
constは変更できないが要素は取り出して変更できてしまう。見かけ上は変更したarrをsetArrしているので混乱するが次のようにする。
import { useState } from "react"
export default function Home() {
const [arr, setArr] = useState([{name: "Sato", age: 23}, {name: "Yogo", age: 12})
function handle() {
arr[1].name = "Taro"
let tmp = arr.map(a => a);
tmp[1].name = "Taro"
console.log(tmp) // 値は変更されている
setArr(tmp) // 値は変更される
}
return (
<>
arr.map(a => <div id={ a }>{ a }</div>)
<div onClick={ handle }></div>
</>
)
return <div onClick={ handle }>{ va }</div> // [1, 2, 3]
}
わからないこと
- constは要素を取り出して変更できてしまっていいのか(そもそもこれ変更できてる?)
- useStateで指定された変数はどう扱われているのか
- こうしたケースのベストプラクティス(おそらくスプレッド構文を使う?)
まだ理解が足りない。
Discussion
クリックによるアップデート情報をすでにfiberNodeのmemorizedStateのqueueに打ち込んあります。
しかし、Object.Is(前のarr,今のarr)の値はtrueだから、再レンダリングを促していないため、新しいarrも計算できてなく、domにも反映されてないとのことです。
スプレッド構文により、arrはまっさら新しいインスタンスとして作成され、Object.Is(前のarr,今のarr)の値はfalseだから、再レンダリングが走ります。当然、arrも計算され、domにも反映されます。
もしTaroを変更しようとした後、別のstateの変化によりでレンダリングが走ったら、Taroもついでに計算でき、domにも反映されます。
例えば
試しているReactバージョンは17です。
ありがとうございます。少しわからないのですが
の時点で(consoleでも明らかな通り)確かにarrの内容は変更されているのに
Object.is(前のarr,今のarr)
がfalseになるのは何故なのでしょうか?arr[1].name = "Taro"だと、arrは変更されてますが、arrは再作成していないです。メモリのアドレスも変わっていないです。前のarrと今のarr実は同じメモリアドレスを指しています。
メモリアドレスを見てるのですね。ありがとうございます理解が進みました。