はじめに
それぞれ同じ構造のオブジェクトを値に持つ配列が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
回答1件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/09/20 04:00
2020/09/20 13:43