簡體   English   中英

在現代 JavaScript 中使用更新的嵌套屬性創建新對象的最佳方法是什么

[英]What's the best way of creating a new object with updated nested properties in modern JavaScript

我有一個帶有一些嵌套屬性的 JavaScript 對象,我想根據某些條件更新這些屬性。 起始對象可能是這樣的:

const options = {
    formatOption: {
        label: 'Model Format',
        selections: {
            name: 'Specific Format',
            value: '12x28',
        }
    },
    heightOption: {
        label: 'Model Height',
        selections: {
            name: 'Specific Height',
            value: '15',
        }
    }
};

我已經提出了一個使用Object.keysreduce和擴展運算符的解決方案,但我想知道這是否是迄今為止最好/更簡潔的方法,或者是否有更好的方法。 我不是在尋找性能最好的選項,而是在尋找“最佳實踐”(如果有的話)或更優雅的方式。

編輯 30/01/20
正如@CertainPerformance在評論中指出的那樣,我的代碼正在改變原始選項變量,因此我正在更改行const option = options[key]; const option = { ...options[key] }; . 我希望這是正確的,並且該函數不會改變原始數據。

const newObject = Object.keys(options).reduce((obj, key) => {
  const option = { ...options[key] };
  const newVal = getNewValue(option.label); // example function to get new values
    // update based on existence of new value and key
    if (option.selections && option.selections.value && newVal) {
      option.selections.value = newVal;
    }
    return {
      ...obj,
      [key]: option,
    };
}, {});

getNewValue是我調用的函數的發明名稱,目的是獲取我正在查看的值的“更新”版本。 為了重現我的情況,您可以替換行const newVal = getNewValue(option.label); const newVal = "bla bla";

由於您使用functional-programming標記了這個 q,因此這里是一種函數式方法。 功能鏡頭是一種高級 FP 工具,因此新手很難掌握。 這只是一個說明,讓您了解如何使用單一方法解決與 getter/setter 相關的幾乎所有任務和問題:

 // functional primitives const _const = x => y => x; // Identity type const Id = x => ({tag: "Id", runId: x}); const idMap = f => tx => Id(f(tx.runId)); function* objKeys(o) { for (let prop in o) { yield prop; } } // Object auxiliary functions const objSet = (k, v) => o => objSetx(k, v) (objClone(o)); const objSetx = (k, v) => o => (o[k] = v, o); const objDel = k => o => objDelx(k) (objClone(o)); const objDelx = k => o => (delete o[k], o); const objClone = o => { const p = {}; for (k of objKeys(o)) Object.defineProperty( p, k, Object.getOwnPropertyDescriptor(o, k)); return p; }; // Lens type const Lens = x => ({tag: "Lens", runLens: x}); const objLens_ = ({set, del}) => k => // Object lens Lens(map => ft => o => map(v => { if (v === null) return del(k) (o); else return set(k, v) (o) }) (ft(o[k]))); const objLens = objLens_({set: objSet, del: objDel}); const lensComp3 = tx => ty => tz => // lens composition Lens(map => ft => tx.runLens(map) (ty.runLens(map) (tz.runLens(map) (ft)))); const lensSet = tx => v => o => // set operation for lenses tx.runLens(idMap) (_const(Id(v))) (o); // MAIN const options = { formatOption: { label: 'Model Format', selections: { name: 'Specific Format', value: '12x28', } }, heightOption: { label: 'Model Height', selections: { name: 'Specific Height', value: '15', } } }; const nameLens = lensComp3( objLens("formatOption")) (objLens("selections")) (objLens("name")); const options_ = lensSet(nameLens) ("foo") (options).runId; // deep update console.log(options_); // reuse of unaffected parts of the Object tree (structural sharing) console.log( options.heightOptions === options_.heightOptions); // true

這只是鏡頭機器的一小部分。 功能性鏡頭具有可組合的良好特性,並在某些情況下利用結構共享。

如果您想以不可變的方式為嵌套屬性設置值,那么您應該考慮采用庫而不是手動進行。

Ramda 提供了一個很好的實現: https : //ramdajs.com/docs/

 const selectionsNameLens = R.lensPath( ['formatOption', 'selections', 'name'], ); const setter = R.set(selectionsNameLens); // --- const data = { formatOption: { label: 'Model Format', selections: { name: 'Specific Format', value: '12x28', }, }, heightOption: { label: 'Model Height', selections: { name: 'Specific Height', value: '15', }, }, }; console.log( setter('Another Specific Format', data), );
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js" integrity="sha256-xB25ljGZ7K2VXnq087unEnoVhvTosWWtqXB4tAtZmHU=" crossorigin="anonymous"></script>

來自CertainPerformance的第一條評論讓我意識到我正在改變原始options變量。 我的第一個想法是用擴展運算符制作一個副本,但擴展運算符只制作一個淺拷貝,所以即使在我的編輯中,我仍然在改變原始對象。
我認為的解決方案是創建一個僅具有更新屬性的新對象,並在 reducer 的末尾合並兩個對象。

編輯
新對象還需要與原始option.selections合並,否則我仍然會覆蓋該級別的現有鍵(即我會覆蓋option.selections.name )。

這是最終的代碼:

const newObject = Object.keys(options).reduce((obj, key) => {
  const option = options[key];
  const newVal = getNewValue(option.label); // example function to get new values
  const newOption = {}; // create a new empty object
  // update based on existence of new value and key
  if (option.selections && option.selections.value && newVal) {
    // fill the empty object with the updated value,
    // merged with a copy of the original option.selections
    newOption.selections = {
      ...option.selections,
      value: newVal
    };
  }
  return {
    ...obj, // accumulator
    [key]: {
      ...option, // merge the old option
      ...newOption, // with the new one
    },
  };
}, {});

向我建議的更簡潔的版本是使用forEach()而不是reduce() 在這種情況下,唯一困難的部分是克隆原始對象。 一種方法是使用 lodash 的_.cloneDeep() ,但有很多選項(請參閱此處)。

這是代碼:

const newObject = _.cloneDeep(options);
Object.keys(newObject).forEach(key => {
    const newVal = getNewValue(newObject[key].label); // example function to get new values
    // update based on existence of new value and key
    if (newObject[key].selections && newObject[key].selections.value && newVal) {
        newObject[key].selections.value = newVal;
    }
});

唯一的問題是forEach()改變了在函數外聲明的值,但是reduce()可以改變它的參數(就像我原來的解決方案中發生的那樣),所以單獨使用reduce()不能解決問題。

我不確定這是最好的解決方案,但對於普通開發人員來說,它肯定比我的第一次嘗試或其他解決方案更具可讀性。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM