質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
オブジェクト

オブジェクト指向において、データとメソッドの集合をオブジェクト(Object)と呼びます。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

配列

配列は、各データの要素(値または変数)が連続的に並べられたデータ構造です。各配列は添え字(INDEX)で識別されています。

Q&A

解決済

1回答

1407閲覧

JavaScript 配列1つを更新したつもりが2つ更新される

ytt451

総合スコア6

オブジェクト

オブジェクト指向において、データとメソッドの集合をオブジェクト(Object)と呼びます。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

配列

配列は、各データの要素(値または変数)が連続的に並べられたデータ構造です。各配列は添え字(INDEX)で識別されています。

0グッド

0クリップ

投稿2020/09/19 19:27

編集2020/09/19 19:53

はじめに

それぞれ同じ構造のオブジェクトを値に持つ配列が2つあります。
このとき1方の配列を更新すると、もう1方も更新されてしまいます。
原因を知りたいです。

概略

js

1const itemsA = [ {key1: value1}, {key2: value2}, ...] 2const itemsB = [ {key1: value1}, {key2: value2}, ...] 3がある場合、 4itemsA.key1 = updatedValue1 5のように個人的に更新したつもりが、 6itemsB.key1 の値も updatedValue1 になるような現象が起きています。

問題のソースコード

商品ページとショッピングカートがあり、
商品を選択してショッピングカートに登録することを模擬したようなプログラムをJavaScriptで作りました。

ソースコードは以下になります。

js

1// 商品一覧 (quantityは数量) 2const products = [ 3 { id: 1, title: '商品1', quantity: 1 }, 4 { id: 2, title: '商品2', quantity: 1 }, 5 { id: 3, title: '商品3', quantity: 1 }, 6]; 7 8// 各ページの状態 9let state = { 10 shopItems: products, // 商品ページ: 初期状態は全商品がある 11 cartItems: [], // カート: 初期状態は空 12}; 13 14// カートに商品を反映する 15function addToCart(shopItem) { 16 // カートに商品があるかのフラグ 17 let isShopItemInCart = false; 18 19 // カートの各商品に対してループ 20 state.cartItems.forEach((cartItem) => { 21 // カートに商品が見つかった場合 22 if (cartItem.id === shopItem.id) { 23 isShopItemInCart = true; 24 // カートの数量に、商品ページの数量を1つ足す 25 cartItem.quantity = cartItem.quantity + shopItem.quantity; 26 } 27 }); 28 29 // カートに商品が見つからなかった場合 30 if (!isShopItemInCart) { 31 // 商品をカートに追加する 32 state.cartItems = [...state.cartItems, shopItem]; 33 } 34} 35 36const shopItem = products[0]; // 商品1 37 38addToCart(shopItem); // 商品1をカートに追加 1回目 39console.log(state); 40 41addToCart(shopItem); // 商品1をカートに追加 2回目 42console.log(state); 43 44addToCart(shopItem); // 商品1をカートに追加 3回目 45console.log(state); 46

このコードを実行すると、個人的に予期した結果と違っていました。

期待する結果

txt

1// 1回目 2{ 3 shopItems: [ 4 { id: 1, title: '商品1', quantity: 1 }, 5 { id: 2, title: '商品2', quantity: 1 }, 6 { id: 3, title: '商品3', quantity: 1 } 7 ], 8 cartItems: [ { id: 1, title: '商品1', quantity: 1 } ] 9} 10 11// 2回目 12{ 13 shopItems: [ 14 { id: 1, title: '商品1', quantity: 1 }, 15 { id: 2, title: '商品2', quantity: 1 }, 16 { id: 3, title: '商品3', quantity: 1 } 17 ], 18 cartItems: [ { id: 1, title: '商品1', quantity: 2 } ] 19} 20 21// 3回目 22{ 23 shopItems: [ 24 { id: 1, title: '商品1', quantity: 1 }, 25 { id: 2, title: '商品2', quantity: 1 }, 26 { id: 3, title: '商品3', quantity: 1 } 27 ], 28 cartItems: [ { id: 1, title: '商品1', quantity: 3 } ] 29}

実際の結果

js

1// 1回目 2{ 3 shopItems: [ 4 { id: 1, title: '商品1', quantity: 1 }, 5 { id: 2, title: '商品2', quantity: 1 }, 6 { id: 3, title: '商品3', quantity: 1 } 7 ], 8 cartItems: [ { id: 1, title: '商品1', quantity: 1 } ] 9} 10 11// 2回目 12{ 13 shopItems: [ 14 { id: 1, title: '商品1', quantity: 2 }, 15 { id: 2, title: '商品2', quantity: 1 }, 16 { id: 3, title: '商品3', quantity: 1 } 17 ], 18 cartItems: [ { id: 1, title: '商品1', quantity: 2 } ] 19} 20 21// 3回目 22{ 23 shopItems: [ 24 { id: 1, title: '商品1', quantity: 4 }, 25 { id: 2, title: '商品2', quantity: 1 }, 26 { id: 3, title: '商品3', quantity: 1 } 27 ], 28 cartItems: [ { id: 1, title: '商品1', quantity: 4 } ] 29}

疑問点

shopItem.quantityに何かを代入された要因がどうしても分かりません。
教えていただけると嬉しいです。

補足(背景)

元はReactのコードなのですが、state更新が予想と違い
JavaScriptだけ抜き出したところ、生JSの部分が分からないと判断した経緯があります。

原型のコードは以下のようになります。

tsx

1import React from 'react'; 2import './tailwind.output.css'; 3 4type Product = { id: number; title: string; price: number; quantity: number }; 5 6type Products = Product[]; 7 8const products: Products = [ 9 { id: 1, title: '商品1', price: 100, quantity: 1 }, 10 { id: 2, title: '商品2', price: 150, quantity: 1 }, 11 { id: 3, title: '商品3', price: 200, quantity: 1 }, 12 { id: 4, title: '商品4', price: 250, quantity: 1 }, 13 { id: 5, title: '商品5', price: 300, quantity: 1 }, 14]; 15 16type SampleProps = {}; 17 18type SampleCartState = { shopItems: Products; cartItems: Products }; 19 20class Sample extends React.Component<SampleProps, SampleCartState> { 21 constructor(props: Readonly<SampleProps>) { 22 super(props); 23 this.state = { shopItems: products, cartItems: [] }; 24 this.addToCart = this.addToCart.bind(this); 25 } 26 27 addToCart(shopItem: Product) { 28 let isShopItemInCart: boolean = false; 29 const cartItems: Products = [...this.state.cartItems]; 30 31 cartItems.forEach((cartItem) => { 32 if (cartItem.id === shopItem.id) { 33 isShopItemInCart = true; 34 cartItem.quantity = Number(cartItem.quantity + shopItem.quantity); 35 } 36 }); 37 38 this.setState({ cartItems: cartItems }); 39 40 if (!isShopItemInCart) { 41 this.setState({ cartItems: [...cartItems, shopItem] }); 42 } 43 } 44 45 getTotalPrice() { 46 let totalPrice: number = 0; 47 this.state.cartItems.forEach((cartItem) => { 48 totalPrice = totalPrice + cartItem.price * cartItem.quantity; 49 }); 50 return totalPrice; 51 } 52 53 render() { 54 return ( 55 <div> 56 {/* 商品一覧 */} 57 <h2 className="text-3xl">商品一覧</h2> 58 {this.state.shopItems.map((shopItem) => ( 59 <div key={shopItem.id}> 60 {shopItem.title} {shopItem.price}円 × {shopItem.quantity}{' '} 61 <button 62 className="bg-gray-400" 63 onClick={() => this.addToCart(shopItem)} 64 > 65 追加 66 </button> 67 </div> 68 ))} 69 70 {/* カート */} 71 <h2 className="text-3xl">カート (計 {this.getTotalPrice()}円)</h2> 72 {this.state.cartItems.map((cartItem) => ( 73 <div key={cartItem.id}> 74 {cartItem.title} {cartItem.price}円 × {cartItem.quantity}75 </div> 76 ))} 77 </div> 78 ); 79 } 80} 81 82export default function App() { 83 return ( 84 <div className="container mx-auto px-4"> 85 <Sample /> 86 </div> 87 ); 88} 89

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

shopItem.quantityに何かを代入された要因がどうしても分かりません。

これを起こしているのは、以下のコードです。
cartItem.quantity = cartItem.quantity + shopItem.quantity;

原因は、cartItemとshopItemが、同じインスタンスであることです。

カートに存在しない商品を追加する時、以下のコードを実行しています。
state.cartItems = [...state.cartItems, shopItem];

このコードは、shopItemを、新たな領域を確保することなく、そのまま、cartItemsの要素として追加する、という処理です。

その為、cartItemsの要素を変更すると、同期して、対応するproductsの要素も変更されます。

商品追加時、クローンを作って、クローンのほうを追加するようにしてください。

投稿2020/09/20 02:46

YT0014

総合スコア1750

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ytt451

2020/09/20 04:00

ありがとうございます! >商品追加時、クローンを作って、クローンのほうを追加するようにしてください。 こちらの助言を元に、 変更前:state.cartItems = [...state.cartItems, shopItem]; 変更後:state.cartItems = [...state.cartItems, { ...shopItem }]; と修正したところ、予想通りの動きになりました。 カートに商品が既にある場合のコードだけを眺めていて、 カートに商品が無かった場合のコードに問題があるなど考えもしていませんでした。 オブジェクトが参照渡しになるのは知っていたつもりですが、なぜか気付いていませんでした...。 誠にありがとうございました。
YT0014

2020/09/20 13:43

無事解決したようですね、おめでとうざいます
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問