javascript - React.js: reagování na změny v externím objektu
Po přečtení oficiální dokumentace react.js chápu, jak by to mělo fungovat správným způsobem, například
- Mám seznam položek v počátečním stavu komponenty
- přidání nové položky pomocí setState aktualizuje stav a spustí aktualizaci uživatelského rozhraní
Co bych měl dělat, pokud používám externí objekt jako model, například nějaké globální pole, které by mělo být dostupné pro části kódu, které nejsou v react.js, NEBO by mohlo být v budoucnu někde modifikováno pomocí webových socketů? Je volání ReactDOM.render po každé akci dobrý způsob? Pokud vím, z hlediska výkonu by to mělo fungovat dobře.
Odpověď č. 1
Stále používáte setState:
let React = require('React');
let externalThing = require('tools/vendor/whoever/external-lib');
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = this.getInitialState();
}
getInitialState() {
// This assumes your external thing is written by someone who was
// smart enough to not allow direct manipulation (because JS has
// no way to monitor primitives for changes), and made sure
// to offer API functions that allow for event handling etc.
externalThing.registerChangeListener(() => this.updateBasedOnChanges(externalThing));
return { data: externalThing.data }
}
updateBasedOnChanges(externalThing) {
// note that setState does NOT automatically trigger render(),
// because React is smarter than that. It will only trigger
// render() if it sees that this new 'data' is different
// (either by being a different thing entirely, or having
// different content)
this.setState({
data: externalThing.data
});
}
render() {
// ...
}
}
Pokud je externí věc, kterou používáte, hrozně napsaná a musíte přímo manipulovat s jejími daty, vaším prvním krokem je napsat pro ni API, abyste s těmito daty ne manipulovali přímo.
let externalData = require('externaldata') // example: this is a shared array
let ExternalDataAPI = new ExternalDataAPI(externalData);
...
A pak se ujistíte, že API má všechny aktualizační a událostní háčky:
class ExternalDataAPI {
constructor(data) {
this.data = data;
this.listeners = [];
}
addListener(fn) {
this.listeners.push(fn);
}
update(...) {
// do something with data
this.listeners.forEach(fn => fn());
}
...
}
Alternativně existují rámce, které to už dělají za vás (flux atd.), ale také do určité míry určují, kolik dalších věcí „by mělo být uděláno“, takže to může být pro vaše potřeby zbytečné.
Odpověď č. 2
Jelikož se vaše otázka týká organizace kódu způsobem, který je snadno spravovatelný, navrhl bych nejprve spojit ReactJS s rámcem typu Flux, jako je Redux nebo Relay.
Pokud to teď chcete přeskočit, můžete svůj projekt organizovat pomocí některých React komponent na vrcholu hierarchie pro ukládání a získávání dat. Například v takové komponentě můžete ve své metodě componentWillMount spustit setTimeout, který periodicky kontroluje váš globální pole a volá setState, když je to vhodné. Metoda render by pak měla obsahovat podřízené komponenty, které přijímají tento stav jako props.
Níže je uveden příklad. Samozřejmě lze časovače nahradit jakoukoli metodou, kterou používáte k odběru změn vašich dat.
// your global object
var globalState = {name: "Sherlock Holmes"}
function onData(callback) {
setInterval(function(){
callback(globalState)
}, 1500)
}
var Child = React.createClass({
render: function() {
return <h1>Hello, {this.props.name}</h1>;
}
});
var Root = React.createClass({
getInitialState: function() {
return {}
},
componentWillMount: function() {
var that = this;
this.props.onData(function(data){
that.setState({external: data})
})
},
render: function() {
if (this.state.external)
return <Child name={this.state.external.name}/>
else
return <div>loading...</div>;
}
});
ReactDOM
.render(<Root onData={onData} />, document.getElementById('container'))<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>