#javascript #reactjs
#javascript #reactjs
Вопрос:
У меня странный сценарий, в котором изменяется prop в компоненте React. Это массив, поэтому технически это не мутация (в стране JavaScript), но все же нет никакого способа его изменить.
Я создал простой пример, чтобы воспроизвести проблему. http://codepen.io/HoraceShmorace/pen/LRXWWb?editors=0011.
Этот компонент:
- принимает prop, содержащий исходные данные «группы»,
- позволяет фильтровать данные этой группы (через текстовое поле),
- задает свойство состояния для отфильтрованных данных группы,
- и затем отображает исходные данные 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