Это может занять эту грань между ответственным и упрямым, но я собираюсь вернуться к тому, как структурировать компонент ReactJS по мере роста сложности и может использовать какое-то направление.
Начиная с AngularJS, я хочу передать свою модель в компонент как свойство и напрямую изменить модель. Или я должен разбить модель на различные свойства state
и собрать ее обратно вместе при отправке назад вверх по течению? Что такое способ ReactJS?
Возьмем, например, редактор блога. Попытка изменить модель напрямую выглядит следующим образом:
var PostEditor = React.createClass({
updateText: function(e) {
var text = e.target.value;
this.props.post.text = text;
this.forceUpdate();
},
render: function() {
return (
<input value={this.props.post.text} onChange={this.updateText}/>
<button onClick={this.props.post.save}/>Save</button>
);
}
});
Что кажется неправильным.
Это скорее способ React, чтобы сделать наше свойство модели text
state
, и скомпилировать его обратно в модель перед сохранением, например:
var PostEditor = React.createClass({
getInitialState: function() {
return {
text: ""
};
},
componentWillMount: function() {
this.setState({
text: this.props.post.text
});
},
updateText: function(e) {
this.setState({
text: e.target.value
});
},
savePost: function() {
this.props.post.text = this.state.text;
this.props.post.save();
},
render: function() {
return (
<input value={this.state.text} onChange={this.updateText}/>
<button onClick={this.savePost}/>Save</button>
);
}
});
Это не требует вызова this.forceUpdate()
, но по мере роста модели (почта может иметь автора, тему, теги, комментарии, оценки и т.д.), компонент начинает становиться действительно сложным.
Является первым методом с ReactLink способом?
Ваш второй подход больше похож на него. React не заботится о моделях так, как он заботится о ценностях и о том, как они протекают через ваше приложение. В идеале ваша модель публикации будет храниться в одном компоненте в корне. Затем вы создаете дочерние компоненты, каждый из которых потребляет части модели.
Вы можете передать обратные вызовы детям, которым необходимо изменить ваши данные, и вызвать их из дочернего компонента.
Модификация this.props или this.state напрямую не является хорошей идеей, потому что React не сможет забрать изменения. Это потому, что React делает неглубокое сравнение вашего сообщения, чтобы определить, изменилось ли оно.
Я сделал это jsfiddle, чтобы показать, как данные могут перетекать из внешнего во внутренний компонент:
http://jsfiddle.net/jxg/M3CLB/
Метод handleClick
показывает 3 способа правильного обновления состояния (im):
var Outer = React.createClass({
getInitialState: function() {
return {data: {value: 'at first, it works'}};
},
handleClick: function () {
// 1. This doesn't work, render is not triggered.
// Never set state directly because the updated values
// can still be read, which can lead to unexpected behavior.
this.state.data.value = 'but React will never know!';
// 2. This works, because we use setState
var newData = {value: 'it works 2'};
this.setState({data: newData});
// 3. Alternatively you can use React immutability helpers
// to update more complex models.
// Read more: http://facebook.github.io/react/docs/update.html
var newState = React.addons.update(this.state, {
data: {value: {$set: 'it works'}}
});
this.setState(newState);
},
render: function() {
return <Inner data={this.state.data} handleClick={this.handleClick} />;
}
});
Обновление 2016: Реакция изменилась, и объяснение "реквизит против государства" стало очень простым. Если компоненту необходимо изменить данные - поставьте его в состояние, иначе в реквизитах. Поскольку реквизиты доступны только для чтения.
Какая точная разница между реквизитами и состоянием?
Вы можете найти хорошее объяснение здесь (полная версия)
setProps
устарела и не должна использоваться. Замена состоит в том, чтобы заново отрендерить компонент и позволить React справиться с различиями.
Из React doc
реквизиты неизменяемы: они передаются от родителя и "принадлежат" родителям. Чтобы реализовать взаимодействия, мы вводим переменное состояние в компонент. this.state является частным компонентом и может быть изменен путем вызова this.setState(). Когда состояние обновляется, компонент повторно отображает себя.
От TrySpace: при обновлении реквизита (или состояния) (через setProps/setState или parent) компонент повторно отображает.
Чтение из Мышление в действии
Пропустите каждый из них и выясните, какой из них является состоянием. Просто задайте три вопроса о каждой части данных:
- Проходит ли он от родителя через реквизит? Если это так, возможно, это не государство.
С течением времени меняется? Если нет, возможно, это не состояние.
Вы можете вычислить его на основе любого другого состояния или реквизита в своем компонент? Если это так, оно не указано.
Я не уверен, отвечаю ли вы на ваш вопрос, но я обнаружил, что, особенно в большом/растущем приложении, шаблон Container/Component работает невероятно хорошо.
По существу у вас есть две компоненты React:
N.B. Этот пример, вероятно, слишком прост, чтобы проиллюстрировать преимущества этого шаблона, так как он достаточно подробен для такого простого случая.
/**
* Container Component
*
* - Manages component state
* - Does plumbing of data fetching/saving
*/
var PostEditorContainer = React.createClass({
getInitialState: function() {
return {
text: ""
};
},
componentWillMount: function() {
this.setState({
text: getPostText()
});
},
updateText: function(text) {
this.setState({
text: text
});
},
savePost: function() {
savePostText(this.state.text);
},
render: function() {
return (
<PostEditor
text={this.state.text}
onChange={this.updateText.bind(this)}
onSave={this.savePost.bind(this)}
/>
);
}
});
/**
* Pure Display Component
*
* - Calculates styling based on passed properties
* - Often just a render method
* - Uses methods passed in from container to announce changes
*/
var PostEditor = React.createClass({
render: function() {
return (
<div>
<input type="text" value={this.props.text} onChange={this.props.onChange} />
<button type="button" onClick={this.props.onSave} />
</div>
);
}
});
Сохраняя логику отображения и управление данными/состоянием раздельно, у вас есть повторно используемый компонент отображения, который:
У вас также есть компонент контейнера, который имеет дело со всей внешней связью. Это должно облегчить гибкость в отношении способа доступа к вашим данным, если вы впоследствии внесете какие-либо серьезные изменения *.
Этот шаблон также делает запись и реализацию модульных тестов намного более простой.
Несколько раз повторив большое приложение React, я обнаружил, что этот шаблон делает вещи относительно безболезненными, особенно когда у вас есть более крупные компоненты с вычисленными стилями или сложные взаимодействия с DOM.
* Прочитайте образец потока и посмотрите на Marty.js, который в значительной степени вдохновил этот ответ (и Я использовал много в последнее время) Redux (и react-redux), которые очень хорошо реализуют этот шаблон.
Я думаю, вы используете анти-шаблон, который Facebook уже объяснил на этой
Здесь вы найдете:
React.createClass({
getInitialState: function() {
return { value: { foo: 'bar' } };
},
onClick: function() {
var value = this.state.value;
value.foo += 'bar'; // ANTI-PATTERN!
this.setState({ value: value });
},
render: function() {
return (
<div>
<InnerComponent value={this.state.value} />
<a onClick={this.onClick}>Click me</a>
</div>
);
}
});
При первом отображении внутреннего компонента он будет иметь {foo: 'bar'} в качестве значения prop. Если пользователь нажимает на привязку, состояние родительского компонента будет обновляться до {value: {foo: 'barbar}}, инициируя процесс повторного рендеринга внутреннего компонента, который получит {foo:' barbar} новое значение для prop.
Проблема заключается в том, что, поскольку родительский и внутренний компоненты имеют ссылку на один и тот же объект, когда объект получает мутацию в строке 2 функции onClick, поддержка внутреннего компонента будет изменяться. Итак, когда процесс повторного рендеринга запущен и будет вызываться componentUpdate, this.props.value.foo будет равен nextProps.value.foo, потому что на самом деле this.props.value ссылается на тот же объект, что и nextProps.value.
Следовательно, поскольку мы пропустим изменения на опоре и короткое замыкание процесса повторного рендеринга, пользовательский интерфейс не будет обновляться с "бара" до "Barbar"
Innercomponents
код Innercomponents
?
text
поля у нас есть методsetText
который выполняет проверку и некоторые другие вещи. Я вижу, как метод (2) работает, еслиsetText
является чистым и возвращает новый экземпляр модели. Однако, еслиsetText
просто обновляет внутреннее состояние, нам все равно нужно вызватьforceUpdate
, верно?forceUpdate
, но в этот момент вы «просачиваетесь» из React. Возможно, лучше передатьsetState()
вызовsetState()
в непрозрачную модель, чтобы избежать необходимости вручную запускать повторные рендеры.