挨拶
@hakeと申します。初投稿です!
オブジェクトの参照とコピーについて勉強し直したので記事にしました!
知っている方も復習する気持ちで読んでいただけると嬉しいです!
以下のコードには問題があります
const [team, setTeam] = useState({
engineer: { reader: "kazu", member: ["eita", "yasu"] },
designer: { reader: "satoshi", member: ["keita", "tadashi"] },
});
// bad
const setEngeneerReader = () => {
// スプレッド構文でteamをコピーする
const newTeam = { ...team };
// 深くネストされた値を変更する
newTeam.engineer.reader = "koki";
console.log(
"prevReader",
team.engineer.reader,
"newReader",
newTeam.engineer.reader
);
// →出力:prevReader koki newReader koki
// この時点でコピー元であるteam.engineer.readerが変更されている
// →意図していない挙動
setTeam(newTeam);
};
Reactにはstateを直接変更しては行けないというルールがあるにもかかわらず、変更してしまっています。
https://ja.reactjs.org/docs/state-and-lifecycle.html#using-state-correctly
こういったミスをしないためにも参照やコピーについて理解が必要
参照とは
他の場所にあるデータを指している情報を含む小さなオブジェクトであり、それ自身の中に(指している)データ自体を含まない。
名前の例
名前を識別子とすると、実際の「人」が値で、値に情報がたくさん詰まっている。
→(例)「のび太」という名前はのび太くん本体(値)を参照していると言える。
のび太くん本体 ← のび太
のび太くん本体 ← のびくん
のび太くん本体 ← のびちゃん
といったように複数の名前(あだ名)がのび太くん本体を参照している
参照によるメリット
複数のコードが参照によってひとつのデータを共有することができ、メモリの節約になる
コピーの種類
シャローコピー(浅いコピー)
オブジェクトの参照をコピーする
コピー元とコピー先で同じ値を参照する
ディープコピー(深いコピー)
オブジェクトの値をコピー
コピー元とコピー先で別の値を参照する
プリミティブ値のコピー
let a = "a";
let b = "b";
a = b;
b = "c";
console.log(a);
// →出力:"b"
console.log(b);
// →出力:"c"
プリミティブ値の場合は値がコピーされる
オブジェクトのコピー
let obj = {p1: "dora", p2: "nobi", p3: "shizu"}
let obj2 = obj
obj2.p1 = "sune"
console.log(obj.p1)
// →出力:"sune"
console.log(obj2.p1)
// →出力:"sune"
オブジェクトの場合は値への参照がコピーされる
→obj2.p1を変更すると元の値が変更されてしまうので注意が必要!
とはいっても、コピー元を変更せずにコピー先を変更したいとき、ありますよね😩
コピー元を変更せずにコピー先を変更する方法
パターン1:Object.assign
let obj = {p1: "dora", p2: "nobi", p3: "shizu"}
let obj2 = Object.assign({}, obj)
obj2.p1 = "sune"
console.log(obj.p1);
// →出力:"dora"
console.log(obj2.p1);
// →出力:"sune"
パターン2:スプレッド演算子
let obj = {p1: "dora", p2: "nobi", p3: "shizu"}
let obj2 = {...obj}
obj2.p1 = "sune"
console.log(obj.p1);
// →出力:"dora"
console.log(obj2.p1);
// →出力:"sune"
問題点
①②の問題点:プロパティにオブジェクトがネストされている場合、そのオブジェクトは参照がコピーされる。
let obj = { p1: "nobi", p2: "shizu", robot: { r1: "doraemon", r2: "dorami" } };
let obj2 = { ...obj };
obj2.robot.r1 = "roomba";
console.log(obj.robot.r1);
// →出力:"roomba"
// 同じ値を参照しているので元の値も変更されてしまう
console.log(obj2.robot.r1);
// →出力:"roomba"
つまり、上記のパターンはシャローコピー(浅いコピー)!!!
→深いネストのオブジェクトをコピーした場合はコビー元も変更されてしまう!!
##ディープコピーしたい場合
パターン1:一度json文字列に変換する
let obj = {p1: "nobita", p2: "shizu", robot: {r1: "doraemon", r2: "dorami"}}
let obj2 = Object.assign({}, JSON.parse(JSON.stringify(obj)))
obj2.robot.r1 = "roomba"
console.log(obj.robot.r1);
// →出力:"doraemon"
console.log(obj2.robot.r1);
// →出力:"roomba"
以下のオブジェクトはdeepCopyできないのであまり推奨されていない
・Dateオブジェクト
・function
・undefined
パターン2:lodashのcloneDeep
let obj = { p1: "nobi", p2: "shizu", robot: { r1: "doraemon", r2: "dorami" } };
let obj2 = _.cloneDeep(obj);
obj2.robot.r1 = "roomba";
console.log(obj.robot.r1);
// →出力:"doraemon"
console.log(obj2.robot.r1);
// →出力:"roomba"
問題のコードをもう一度見てみよう!
const [team, setTeam] = useState({
engineer: { reader: "kazu", member: ["eita", "yasu"] },
designer: { reader: "satoshi", member: ["keita", "tadashi"] },
});
// bad
const setEngeneerReader = () => {
// teamをディープコピーする
const newTeam = _.cloneDeep(team);
// 深くネストされた値を変更する
newTeam.engineer.reader = "koki";
console.log(
"prevReader",
team.engineer.reader,
"newReader",
newTeam.engineer.reader
);
// →出力:prevReader koki newReader koki
// この時点でコピー元であるteam.engineer.readerが変更されている
// →意図していない挙動
setTeam(newTeam);
};
とは言っても、なるべくネストしないように定義するべきですね!!!
おまけ
分割代入
const obj = {
p: "dora",
};
let { p } = obj;
p = "nobi";
console.log(obj.p, p);
// →出力:"dora" "nobi"
const obj2 = {
p1: {
p2: "dora",
},
};
let { p1 } = obj2;
p1.p2 = "nobi";
console.log(obj2.p1.p2, p1.p2);
// →出力:"nobi" "nobi"
分割代入の場合オブジェクトがネストされている場合、参照がコピーされるので注意!!
新しくインスタンス化
class Team {
public id: number = 0;
public engineer: { reader: string; front: string[]; back: string[] } = {
reader: "",
front: [],
back: [],
};
public designer: { reader: string; member: string[] } = {
reader: "",
member: [],
};
constructor(init?: Partial<Team>) {
// assign
Object.assign(this, init);
}
}
const team1 = new Team({
id: 1,
engineer: {
reader: "dora",
front: ["atom", "haro"],
back: ["r2", "c3"],
},
designer: {
reader: "roomba",
member: ["braaba", "eufy"],
},
});
const team2 = new Team(team1);
team2.engineer.reader = "ashimo";
console.log(team1.engineer.reader, team2.engineer.reader);
// →出力:"ashimo" "ashimo"
newでのインスタンス化はシャローコピーになる
▼複業でスキルを活かしてみませんか?複業クラウドの登録はこちら!
https://talent.aw-anotherworks.com/?login_type=none