2つのオブジェクトの配列があります。
genericItems: Item[] = [];
backupData: Item[] = [];
HTMLテーブルにgenericItems
dataを入力しています。テーブルは変更可能です。 backUpData
で行ったすべての変更を取り消すためのリセットボタンがあります。この配列にはサービスが入力されます:
getGenericItems(selected: Item) {
this.itemService.getGenericItems(selected).subscribe(
result => {
this.genericItems = result;
});
this.backupData = this.genericItems.slice();
}
私の考えでは、ユーザーの変更は最初のアレイに反映され、2番目のアレイはリセット操作のバックアップとして使用できます。私がここで直面している問題は、ユーザーがテーブルを変更するときです(genericItems[])
2番目の配列backupData
も変更されます。
これはどのように起こり、これを防ぐ方法ですか?
これを試して :
配列のクローン:
const myClonedArray = Object.assign([], myArray);
オブジェクトのクローン:
const myClonedObject = Object.assign({}, myObject);
javascriptのクローン配列とオブジェクトの構文は異なります。遅かれ早かれ誰もが違いを難しい方法で学び、ここで終わることになります。
TypeScriptおよびES6では、配列とオブジェクトにスプレッド演算子を使用できます。
const myClonedArray = [...myArray]; // This is ok for [1,2,'test','bla']
// But wont work for [{a:1}, {b:2}].
// A bug will occur when you
// modify the clone and you expect the
// original not to be modified.
// The solution is to do a deep copy
// when you are cloning an array of objects.
オブジェクトのディープコピーを行うには、外部ライブラリが必要です。
import * as cloneDeep from 'lodash/cloneDeep';
const myClonedArray = cloneDeep(myArray); // This works for [{a:1}, {b:2}]
スプレッド演算子はオブジェクトに対しても機能しますが、浅いコピー(子の最初のレイヤー)のみを行います
const myShallowClonedObject = {...myObject}; // Will do a shallow copy
// and cause you an un expected bug.
オブジェクトのディープコピーを行うには、外部ライブラリが必要です。
import * as cloneDeep from 'lodash/cloneDeep';
const deeplyClonedObject = cloneDeep(myObject); // This works for [{a:{b:2}}]
マップまたは他の同様のソリューションを使用しても、オブジェクトの配列を深く複製するのに役立ちません。新しいライブラリを追加せずにこれを行う簡単な方法は、JSON.stringfyを使用してからJSON.parseを使用することです。
あなたの場合、これはうまくいくはずです:
this.backupData = JSON.parse(JSON.stringify( genericItems));
配列を複製する最も簡単な方法は
backUpData = genericItems.concat();
これにより、配列インデックス用の新しいメモリが作成されます
PrimeNg DataTableでも同じ問題があります。試して泣いた後、このコードを使用して問題を解決しました。
private deepArrayCopy(arr: SelectItem[]): SelectItem[] {
const result: SelectItem[] = [];
if (!arr) {
return result;
}
const arrayLength = arr.length;
for (let i = 0; i <= arrayLength; i++) {
const item = arr[i];
if (item) {
result.Push({ label: item.label, value: item.value });
}
}
return result;
}
バックアップ値を初期化するため
backupData = this.deepArrayCopy(genericItems);
変更をリセットするため
genericItems = this.deepArrayCopy(backupData);
魔法の弾丸は、コンストラクターを呼び出す代わりに{}
を使用してアイテムを再作成することです。 new SelectItem(item.label, item.value)
を試しましたが、うまくいきません。
これを試して:
[https://lodash.com/docs/4.17.4#clone][1]
var objects = [{ 'a': 1 }, { 'b': 2 }];
var shallow = _.clone(objects);
console.log(shallow[0] === objects[0]);
// => true
コードの次の行は、新しい配列を作成し、genericItems
からすべてのオブジェクト参照をその新しい配列にコピーし、backupData
に割り当てます。
this.backupData = this.genericItems.slice();
そのため、backupData
とgenericItems
は異なる配列ですが、同じオブジェクト参照が含まれています。
ライブラリを持ち込んで、ディープコピーを行うことができます(@LatinWarriorが述べたように)。
ただし、Item
があまり複雑でない場合は、clone
メソッドを追加して、オブジェクトを自分でディープクローンすることができます。
class Item {
somePrimitiveType: string;
someRefType: any = { someProperty: 0 };
clone(): Item {
let clone = new Item();
// Assignment will copy primitive types
clone.somePrimitiveType = this.somePrimitiveType;
// Explicitly deep copy the reference types
clone.someRefType = {
someProperty: this.someRefType.someProperty
};
return clone;
}
}
次に、各アイテムでclone()
を呼び出します。
this.backupData = this.genericItems.map(item => item.clone());
以下のコードは、最初のレベルのオブジェクトをコピーするのに役立ちます
let original = [{ a: 1 }, {b:1}]
const copy = [ ...original ].map(item=>({...item}))
したがって、以下の場合、値はそのままです
copy[0].a = 23
console.log(original[0].a) //logs 1 -- value didn't change voila :)
この場合は失敗します
let original = [{ a: {b:2} }, {b:1}]
const copy = [ ...original ].map(item=>({...item}))
copy[0].a.b = 23;
console.log(original[0].a) //logs 23 -- lost the original one :(
最終アドバイス:
Lodash cloneDeep
APIを選択すると、元のオブジェクトから完全に参照解除されたオブジェクト内のオブジェクトをコピーするのに役立ちます。これは、個別のモジュールとしてインストールできます。
ドキュメントを参照:https://github.com/lodash/lodash
マップ機能を使用できます
toArray= fromArray.map(x => x);
あなたが望むのは、オブジェクトのディープコピーのようです。 Object.assign()
を使用しないのはなぜですか?ライブラリは必要ありません、そしてワンライナー:)
getGenericItems(selected: Item) {
this.itemService.getGenericItems(selected).subscribe(
result => {
this.genericItems = result;
this.backupDate = Object.assign({}, result);
//this.backupdate WILL NOT share the same memory locations as this.genericItems
//modifying this.genericItems WILL NOT modify this.backupdate
});
}
Object.assign()
の詳細: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
配列のコピーをどこで行っているかを間違えたようです。以下の私の説明と、データを以前の状態にリセットするのに役立つコードにわずかな変更を加えてください。
あなたの例では、次のことが起こっていることがわかります:
3番目のポイントをこの順序で実行したくないと考えるのは正しいですか?
これが良いでしょうか?
これを試してください:
getGenericItems(selected: Item) {
this.itemService.getGenericItems(selected).subscribe(
result => {
// make a backup before you change the genericItems
this.backupData = this.genericItems.slice();
// now update genericItems with the results from your request
this.genericItems = result;
});
}