Каков наилучший способ создания нового объекта с обновленными вложенными свойствами в современном JavaScript - PullRequest
0 голосов
/ 30 января 2020

У меня есть JavaScript объект с некоторыми вложенными свойствами, который я хочу обновить в зависимости от некоторых условий. Начальным объектом может быть что-то вроде:

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

Я нашел решение, использующее Object.keys, reduce и оператор распространения, но я хотел бы знать, является ли это лучшим / более краткий путь на сегодняшний день или, если есть лучший способ. Я ищу не самый эффективный вариант, а «лучшую практику» (если она есть) или более элегантный способ.

РЕДАКТИРОВАТЬ 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";

Ответы [ 4 ]

1 голос
/ 31 января 2020

Если вы хотите установить значение для вложенного свойства неизменным образом, тогда вам следует рассмотреть возможность принятия библиотеки, а не делать это вручную.

  • В FP есть концепция lenses

Рамда обеспечивает хорошую реализацию: 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>
1 голос
/ 30 января 2020

Поскольку вы пометили эту букву functional-programming, это функциональный подход. Функциональные линзы - это усовершенствованный инструмент FP, и, следовательно, его трудно обрабатывать новичкам. Это просто иллюстрация, чтобы дать вам представление о том, как вы можете решить практически все задачи и проблемы, связанные с геттерами / сеттерами, с помощью одного подхода:

// 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

Это лишь крошечная часть механизма объектива. Функциональные линзы обладают хорошим свойством компоновки и использования структурного разделения в некоторых случаях.

0 голосов
/ 06 февраля 2020

Более краткая версия, которая была мне предложена, - использовать forEach() вместо reduce(). В этом случае единственной трудной частью будет клонирование исходного объекта. Один из способов - использовать loda sh s _.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().

Я не уверен, что это лучшее решение, но оно, безусловно, гораздо более читабельно для среднего разработчика, чем моя первая попытка или другие решения.

0 голосов
/ 31 января 2020

Первый комментарий от CertainPerformance заставил меня понять, что я изменял исходную переменную options. Моя первая идея состояла в том, чтобы сделать копию с помощью оператора распространения, но оператор распространения делает только мелкую копию, поэтому даже в своем редактировании я все еще мутировал в исходном объекте.
То, что я считаю решением, - это создать новый объект только с обновленным свойством, и объединить два объекта в конце редуктора.

РЕДАКТИРОВАТЬ
Новый объект также необходимо объединить с исходным option.selections, в противном случае я все равно перезаписал бы существующие ключи на этом уровне (ie Я бы перезаписал 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
    },
  };
}, {});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...