为什么按钮点击触发器与 setTimeout() 触发器不同?
Why button click trigger is different from setTimeout() trigger?
考虑以下几乎相同的两个片段。
区别是:
- 第一个使用
setTimeout()
触发事件
- 第二个在点击按钮时触发事件
如果您检查控制台,您会看到代码段 1 中的最后两行是:
App rendering 1 folder(s)
Observed js
在片段 2 中是:
Observed js
App rendering 1 folder(s)
问题:为什么顺序反了?
代码段 1:setTimeout() 触发器
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>
代码段 2:按钮触发器
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>
它们 运行 的顺序不同,因为 React 尝试将 setState()
调用一起批处理,因此调用 setState()
不会导致组件同步重新渲染,而是等待直到事件回调 returns.
但是,只有当且仅当您对 setState
的调用是 React 驱动事件的结果时,它才会这样做,例如 onClick
是。当您使用 setTimeout
时,React(当前)无法知道您何时完成,因此无法将它们一起批处理。相反,它会立即同步重新呈现。
据我所知,React 文档只是顺便间接提到了这种行为:
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
如果你想让 React 批处理东西,你需要将你的回调代码包装在 ReactDOM.unstable_batchedUpdates
中,顾名思义,它不是一个稳定的 API,所以它可以(并且很可能会)在没有警告的情况下更改。
setTimeout(() => {
ReactDOM.unstable_batchedUpdates(() => {
console.log('Emitting event');
this.events$.next({
type: 'ADD_FOLDER',
name: 'js',
permissions: 400
});
});
}, 1000);
理想情况下,您的代码结构应该与顺序无关。
考虑以下几乎相同的两个片段。
区别是:
- 第一个使用
setTimeout()
触发事件 - 第二个在点击按钮时触发事件
如果您检查控制台,您会看到代码段 1 中的最后两行是:
App rendering 1 folder(s)
Observed js
在片段 2 中是:
Observed js
App rendering 1 folder(s)
问题:为什么顺序反了?
代码段 1:setTimeout() 触发器
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>
代码段 2:按钮触发器
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>
它们 运行 的顺序不同,因为 React 尝试将 setState()
调用一起批处理,因此调用 setState()
不会导致组件同步重新渲染,而是等待直到事件回调 returns.
但是,只有当且仅当您对 setState
的调用是 React 驱动事件的结果时,它才会这样做,例如 onClick
是。当您使用 setTimeout
时,React(当前)无法知道您何时完成,因此无法将它们一起批处理。相反,它会立即同步重新呈现。
据我所知,React 文档只是顺便间接提到了这种行为:
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
如果你想让 React 批处理东西,你需要将你的回调代码包装在 ReactDOM.unstable_batchedUpdates
中,顾名思义,它不是一个稳定的 API,所以它可以(并且很可能会)在没有警告的情况下更改。
setTimeout(() => {
ReactDOM.unstable_batchedUpdates(() => {
console.log('Emitting event');
this.events$.next({
type: 'ADD_FOLDER',
name: 'js',
permissions: 400
});
});
}, 1000);
理想情况下,您的代码结构应该与顺序无关。