Поддержка в компоненте React подвергается мутации

#javascript #reactjs

#javascript #reactjs

Вопрос:

У меня странный сценарий, в котором изменяется prop в компоненте React. Это массив, поэтому технически это не мутация (в стране JavaScript), но все же нет никакого способа его изменить.

Я создал простой пример, чтобы воспроизвести проблему. http://codepen.io/HoraceShmorace/pen/LRXWWb?editors=0011.

Этот компонент:

  1. принимает prop, содержащий исходные данные «группы»,
  2. позволяет фильтровать данные этой группы (через текстовое поле),
  3. задает свойство состояния для отфильтрованных данных группы,
  4. и затем отображает исходные данные prop и отфильтрованные данные состояния параллельно.

Данные группы представляют собой список групп, каждая из которых имеет имя и массив дочерних объектов. Эти дочерние объекты будут либо представлять людей {name:[value]} , либо несколько групп, образующих иерархию.

Фильтрация данных группы выполняется правильно. Однако по какой-то причине и состояние, И prop обновляются.

Вот JS:

 /**
 * Define the component.
 */
class TestComponent extends React.Component {
  constructor(props) {
    super(props)

    const {initialGroups} = this.props

    //Initialize filtered groups with the intial groups
    this.state = {
      filteredGroups: initialGroups
    }
  }

  /**
   * Simply outputs the props and the state to the screen,
   * as well as an input field that filters the group data.
   */
  render() {
    return (
      <div>
        <input type="text" placeholder="Filter by name" onChange={onChange.bind(this)} />
        <section id="output">
          <pre>
            <h1>props:</h1>
            {JSON.stringify(this.props,null,2)}
          </pre>
          <pre>
            <h1>state:</h1>
            {JSON.stringify(this.state,null,2)}
          </pre>
        </section>
      </div>
    )
  }
}

TestComponent.propTypes = {
  initialGroups: React.PropTypes.array.isRequired
}

/**
 * Define event handler for text input change, 
 * which filters group data recursively,
 * the result of which gets saved in state.
 */
function onChange(e) {
  //Get input value
  const filterInputValue = e.target.value

  //Get initial group data to filter. Note how this is copying the array, non-mutatingly, just in case.
  const initialGroups = [...this.props.initialGroups] 

  //Filter the group data (function defined below).
  const filteredGroups = filterGroupsByPerson(initialGroups) 

  //Update the state
  this.setState({filteredGroups})

  //Recursive filtering function
  function filterGroupsByPerson(arr) {
    return arr.filter(item=>{
      const hasChildren = item.children instanceof Array amp;amp; item.children.length > 0

      //Having children implies this is a group. Don't test the name, just try to filter the children.
      if(hasChildren) {
        //Filter children array recursively
        item.children = filterGroupsByPerson(item.children) 
        return item.children.length > 0
      }

      //This is a person, not a group. Filter on the name.
      const filterRegex = new RegExp(filterInputValue, 'i')
      const filterResults = filterRegex.test(item.name)
      return filterResults
    })
  }
}

/**
 * Mock up the initial group data
 */
const InitialGroups = [
  {
    "name": "Engineering",
    "children": [
      {"name": "Gates"},
      {"name": "Allen"},
      {"name": "Jobs"},
      {"name": "Wozniak"},
      {
        "name": "Consultants",
        "children": [
          {"name": "Jess"},
          {"name": "Cece"},
          {"name": "Winston"},
          {"name": "Schmidt"}
        ]
      },
    ]
  }
]

/**
 * Implement component, passing initial group data as a prop.
 */
ReactDOM.render(<TestComponent initialGroups={InitialGroups}/>, document.getElementById('test-component'))
  

И немного HTML:

 <div id="test-component"></div>
  

… и CSS:

 #output {
  display:flex;
}

#output pre {
  background-color: #eeeecc;
  margin-right: 20px;
}
  

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

1. [...this.props.initialGroups] выполняет только неглубокую копию. Объекты внутри массива не копируются, поэтому item.children = filterGroupsByPerson(item.children) это причина эффекта, который вы видите. Другими словами: Props и state, похоже, обновляются, потому что они ссылаются на один и тот же объект. Похоже, вы хотите выполнить глубокую копию.

2. Почему бы и нет? ……

3. Вы хотите сказать, что элементы внутри нового массива являются ссылками на те же элементы в старом массиве?

4. ДА. Если у вас есть var foo = [{x: 42}]; и var bar = [...foo]; , то foo[0] === bar[0] есть true . Изменение bar[0].x затронет foo , поскольку foo[0] и bar[0] ссылаются на один и тот же объект.

5. На самом деле это не особенно элегантно и зависит от синтаксического анализа строк; почему бы не использовать любую из существующих clone реализаций (Lodash, что угодно)?

Ответ №1:

вы можете сделать:

 const filterInputValue = e.target.value
  const intialGroupClone = JSON.parse(JSON.stringify(this.props.initialGroups))

  const filteredGroups = filterGroupsByPerson(intialGroupClone) 

  //Update the state
  this.setState({filteredGroups})
  

здесь работает ручкаhttp://codepen.io/finalfreq/pen/YGRxoa