Как копировать объекты с обновлениями во вложенные объекты в Typescript?

#javascript #reactjs #typescript

Вопрос:

У меня есть компонент React, в котором есть некоторые поля формы:

 <TextField
  label="Description"
  id="description"
  value={company.companyDescription}
  onChange={updateFormField("companyDescription")}
></TextField>
 

и функция, которая обновляет состояние моей компании всякий раз, когда меняются значения:

 const updateFormField = (property: string) => (event: ChangeEvent<HTMLInputElement>) => {
  setCompany(prev => ({ ...prev, [property]: event.target.value }))
}
 

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

Моя проблема в том, что в компании есть вложенные свойства, такие как company.location.address :

 {
  name: "some company",
  location: {
    address: "some street"
  }
  // ...
}
 

Есть ли способ рефакторинга вышеуказанной функции для обновления вложенных свойств? Например, что-то вроде:

 const updateFormField = (property: string[]) => (event: ChangeEvent<HTMLInputElement>) => {
  setCompany(prev => ({ ...prev, [...property]: event.target.value }))
}
 

Комментарии:

1. Вы имеете в виду, что хотите объединить старые/новые объекты для вашего company[property] ?

2. Я обновил свой ответ. Я каждый раз создаю новый объект (вот как работает React). Я бы хотел перезаписать определенные свойства, например company.name , или company.location.address .

Ответ №1:

Я не думаю, что для этого есть особенно простое решение, однако должна быть возможность выполнить циклический выбор/добавление нового пути:

 const updateFormField = (property: string[]) => (event: ChangeEvent<HTMLInputElement>) => {
  setCompany(prev => {
    // Take a copy of the state
    const newObj = { ...prev }
    // Set up a refernce to that object
    let selected = newObj
    // Loop through each property string in the array
    for (let i = 0; i < property.length; i  ) {
        // If we're at the end of the properties, set the value
        if (i === property.length - 1) {
            selected[property[i]] = event.target.value
        } else {
            // If the property doesn't exist, or is a value we can't add a new property to, set it to a new object
            if (typeof selected[property[i]] !== 'object') {
                selected[property[i]] = {}
            } 
            // Update our refernce to the currently selected property and repeat
            selected = selected[property[i]]
        }
    }
    // Return the object with each nested property added
    return newObj
  )}
}
 

Простой JS-рабочий пример того же метода:

 const test = (prev, property, value) => {
  const newObj = { ...prev
  }
  let selected = newObj
  for (let i = 0; i < property.length; i  ) {
    if (i === property.length - 1) {
      selected[property[i]] = value
    } else {
      if (typeof selected[property[i]] !== 'object') {
        selected[property[i]] = {}
      }
      selected = selected[property[i]]
    }
  }
  return newObj
}

console.log(test(
  {"a": "1"},
  ["b", "c", "d"],
  100
))

console.log(test(
  {"a": "1"}, 
  ["a", "b"], 
  100
))

console.log(test(
  {"a": {"b": {"c": 1}}},
  ["a", "b", "c"],
  100
)) 

Комментарии:

1. Спасибо за ваши усилия. Я подозревал, что так оно и было. 🙁

2. Нет проблем, надеюсь, кто-то еще сможет ответить с помощью оператора/шаблона, о котором я не знаю, но, к сожалению, я с ним не сталкивался.

Ответ №2:

Object.assign() и динамический поиск внутренней ссылки должны это сделать. Я предполагаю, что тип ввода строки[] выше указывает, что вложенный путь представляет собой массив, такой как [«компания», «местоположение», «адрес»]:

 const updateFormField = (property: string[]) => (event: ChangeEvent<HTMLInputElement>) => {
  setCompany(prev => {
    const copy = Object.assign({}, prev);
    let ref = copy;
    for (let i = 0; i < property.length - 1; i  ) {
      ref = ref[property[i]];
    }
    ref[property[property.length - 1]] = event.target.value
    return copy;
  });
}