C’est peut-être une différence entre répondre et donner son opinion, mais je reviens encore et encore sur la manière de structurer un composant ReactJS à mesure que la complexité augmente et que l’on pourrait utiliser une certaine direction.
Venant d'AngularJS, je souhaite transmettre mon modèle au composant en tant que propriété et le faire modifier directement par le composant. Ou devrais-je scinder le modèle en diverses propriétés state
et le compiler ensemble lors de la restitution en amont? Quelle est la méthode ReactJS?
Prenons l'exemple d'un éditeur de publication de blog. Essayer de modifier directement le modèle se termine ainsi:
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>
);
}
});
Ce qui semble faux.
S’agit-il plus de la méthode React pour rendre notre propriété de modèle text
state
et la recompiler dans le modèle avant de sauvegarder, comme suit:
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>
);
}
});
Cela ne nécessite pas un appel à this.forceUpdate()
, mais à mesure que le modèle se développe (une publication peut avoir un auteur, un sujet, des balises, des commentaires, des notes, etc.), le composant commence à devenir vraiment compliqué.
La première méthode avec ReactLink est-elle la voie à suivre?
Votre deuxième approche est plus comme ça. React se soucie moins des modèles que de values et de la manière dont ils circulent dans votre application. Idéalement, votre modèle de publication serait stocké dans un seul composant à la racine. Vous créez ensuite des composants enfants qui consomment chacun des parties du modèle.
Vous pouvez transmettre des rappels aux enfants qui doivent modifier vos données et les appeler à partir du composant enfant.
La modification directe de this.props ou this.state n’est pas une bonne idée, car React ne sera pas en mesure de prendre en compte les modifications. En effet, React effectue une comparaison superficielle de votre accessoire de poste pour déterminer s’il a changé.
J'ai fait ce jsfiddle pour montrer comment les données peuvent circuler d'un composant externe vers un composant interne:
http://jsfiddle.net/jxg/M3CLB/
La méthode handleClick
montre 3 façons de mettre à jour (im) correctement l'état:
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's 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} />;
}
});
Mise à jour 2016: React est modifié et l'explication "props vs state" est devenue très simple. Si un composant doit modifier des données, mettez-les dans un état, sinon, dans les accessoires. Parce que props sont en lecture seule maintenant.
Quelle est la différence exacte entre les accessoires et l'état?
Vous pouvez trouver une bonne explication ici (version complète)
De React doc
les accessoires sont immuables: ils sont passés du parent et sont "possédés" par le parent. Pour implémenter des interactions, nous introduisons l'état mutable dans le composant. this.state est une propriété privée du composant et peut être modifié en appelant this.setState (). Lorsque l'état est mis à jour, le composant se redresse lui-même.
From TrySpace : lorsque les propriétés (ou l'état) sont mises à jour (via setProps/setState ou parent), le composant effectue également le rendu.
Une lecture de Think in React :
Passons en revue chacun et déterminons lequel est l'état. Simplement posez trois questions sur chaque donnée:
- Est-il transmis d'un parent via des accessoires? Si c'est le cas, ce n'est probablement pas Etat.
Cela change-t-il avec le temps? Sinon, ce n'est probablement pas l'état.
Pouvez-vous le calculer en fonction de tout autre état ou accessoire de votre composant? Si c'est le cas, ce n'est pas l'état.
Je ne sais pas si je réponds à votre question, mais j'ai constaté que, notamment dans une application de grande taille/en croissance, le modèle Conteneur/Composant fonctionnait incroyablement bien.
Vous avez essentiellement deux composants React:
N.B. Cet exemple est probablement trop simple pour illustrer les avantages de ce modèle, car il est assez détaillé pour un cas aussi simple.
/**
* 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>
);
}
});
En séparant la logique d'affichage et la gestion des données/états, vous disposez d'un composant d'affichage réutilisable qui:
Vous avez également un composant conteneur qui traite de toutes les communications externes. Cela devrait permettre de faire preuve de plus de souplesse quant à la manière dont vous accédez à vos données si vous apportez des modifications sérieuses ultérieurement *.
Ce modèle facilite également l'écriture et la mise en œuvre des tests unitaires.
Après avoir itéré une grande application React à quelques reprises, j'ai constaté que ce modèle évite les problèmes liés à la douleur, notamment lorsque vous avez des composants plus volumineux avec des styles calculés ou des interactions DOM complexes.
* Lisez sur le modèle de flux, et jetez un oeil à Marty.js , qui a largement inspiré cette réponse (et j'en ai beaucoup utilisé ces derniers temps) Redux (et react-redux ), qui implémente extrêmement bien ce modèle.
Note pour ceux qui liront ceci en 2018 ou après:
React a beaucoup évolué depuis la rédaction de cette réponse, notamment avec l’introduction de Hooks . Cependant, la logique de gestion d'état sous-jacente de cet exemple reste la même et, plus important encore, les avantages que vous obtenez en maintenant vos états et votre logique de présentation séparés s'appliquent toujours de la même manière.
Je pense que vous utilisez un anti-motif que Facebook a déjà expliqué à ce moment link
Voici ce que vous trouvez:
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>
);
}
});
La première fois que le composant interne est rendu, il aura {foo: 'bar'} comme valeur prop. Si l'utilisateur clique sur l'ancre, l'état du composant parent est mis à jour pour {valeur: {foo: 'barbar'}}, ce qui déclenche le processus de restitution du composant interne, qui recevra {foo: 'barbar'} sous la forme la nouvelle valeur pour l'accessoire.
Le problème est que, puisque les composants parent et interne partagent une référence au même objet, lorsque l'objet est muté sur la ligne 2 de la fonction onClick, l'accessoire dont disposait le composant interne allait changer. Ainsi, lorsque le processus de re-rendu commence et que shouldComponentUpdate est appelé, this.props.value.foo sera égal à nextProps.value.foo, car en fait, this.props.value fait référence au même objet que nextProps.value.
Par conséquent, puisque nous allons manquer le changement sur l'hélice et court-circuiter le processus de re-rendu, l'interface utilisateur ne sera pas mise à jour de 'bar' à 'barbar'