问题
Consider the following, almost identical, two snippets.
The difference is:
- the first one uses
setTimeout()
to trigger the event - the second one triggers the event when the button is clicked
If you check the console, you'll see that the last two lines in Snippet 1 are:
App rendering 1 folder(s)
Observed js
and in Snippet 2 are:
Observed js
App rendering 1 folder(s)
Question: Why is the order reversed?
setTimeout() playground
Button playground
Snippet 1: setTimeout() trigger
class App extends React.Component {
constructor() {
super();
this.events$ = new Rx.Subject();
this.eventsByName$ = this.events$.groupBy(e => e.name);
this.state = {};
setTimeout(() => {
console.log('Emitting event');
this.events$.next({
type: 'ADD_FOLDER',
name: 'js',
permissions: 400
});
}, 1000);
}
componentDidMount() {
this.eventsByName$.subscribe(folderEvents$ => {
const folder = folderEvents$.key;
console.log(`New stream for "${folder}" created`);
folderEvents$.subscribe(e => {
console.log(`Observed ${e.name}`);
});
this.setState({
[folder]: folderEvents$
});
});
}
render() {
const folders = Object.keys(this.state);
console.log(`App rendering ${folders.length} folder(s)`);
return (
<div>
{
folders.map(folder => (
<div key={folder}>
{folder}
</div>
))
}
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('app')
);
<head>
<script src="https://unpkg.com/rxjs@5.2.0/bundles/Rx.js"></script>
<script src="https://unpkg.com/react@15.4.2/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@15.4.2/dist/react-dom.js"></script>
</head>
<body>
<div id="app"></div>
</body>
Snippet 2: Button trigger
class App extends React.Component {
constructor() {
super();
this.events$ = new Rx.Subject();
this.eventsByName$ = this.events$.groupBy(e => e.name);
this.state = {};
}
componentDidMount() {
this.eventsByName$.subscribe(folderEvents$ => {
const folder = folderEvents$.key;
console.log(`New stream for "${folder}" created`);
folderEvents$.subscribe(e => {
console.log(`Observed ${e.name}`);
});
this.setState({
[folder]: folderEvents$
});
});
}
onClick = () => {
console.log('Emitting event');
this.events$.next({
type: 'ADD_FOLDER',
name: 'js',
permissions: 400
});
};
render() {
const folders = Object.keys(this.state);
console.log(`App rendering ${folders.length} folder(s)`);
return (
<div>
<button onClick={this.onClick}>
Add event
</button>
<div>
{
folders.map(folder => (
<div key={folder}>
{folder}
</div>
))
}
</div>
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('app')
);
<head>
<script src="https://unpkg.com/rxjs@5.2.0/bundles/Rx.js"></script>
<script src="https://unpkg.com/react@15.4.2/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@15.4.2/dist/react-dom.js"></script>
</head>
<body>
<div id="app"></div>
</body>
回答1:
They are run in a different order because React tries to batch setState()
calls together, so calling setState()
does not cause the component to re-render synchronously, but instead waits until the event callback returns.
However, it only does this if and only if your call to setState
was the result of a React-driven event, like onClick
is. When you're using setTimeout
, React (currently) has no way to know when you're done, so it cannot batch them together. Instead, it synchronously re-renders right then.
Best I can tell, React docs only mention this behavior indirectly in passing:
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.
https://facebook.github.io/react/docs/react-component.html#setstate
If you want React to batch things, you would need to wrap your callback code inside ReactDOM.unstable_batchedUpdates
, which as the name suggests is not a stable API so it can (and likely will) change without warning.
setTimeout(() => {
ReactDOM.unstable_batchedUpdates(() => {
console.log('Emitting event');
this.events$.next({
type: 'ADD_FOLDER',
name: 'js',
permissions: 400
});
});
}, 1000);
Ideally, your code would be structured in a way in which the order does not matter.
来源:https://stackoverflow.com/questions/42659326/why-button-click-trigger-is-different-from-settimeout-trigger