为什么按钮点击触发器与 setTimeout() 触发器不同? [英] Why button click trigger is different from setTimeout() trigger?

查看:38
本文介绍了为什么按钮点击触发器与 setTimeout() 触发器不同?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑以下几乎相同的两个片段.

区别在于:

  • 第一个使用 setTimeout() 来触发事件
  • 第二个在按钮被点击时触发事件

如果您检查控制台,您会看到代码段 1 中的最后两行是:

App 渲染 1 个文件夹观察js

在代码片段 2 中是:

观察到的js应用渲染 1 个文件夹

问题:为什么顺序颠倒了?

setTimeout() 游乐场

按钮游乐场

<小时>

代码段 1:setTimeout() 触发器

class App extends React.Component {构造函数(){极好的();this.events$ = new Rx.Subject();this.eventsByName$ = this.events$.groupBy(e => e.name);this.state = {};setTimeout(() => {console.log('发射事件');this.events$.next({类型:'ADD_FOLDER',名称:'js',权限:400});}, 1000);}componentDidMount() {this.eventsByName$.subscribe(folderEvents$ => {const 文件夹 = folderEvents$.key;console.log(`已创建${folder}"的新流`);folderEvents$.subscribe(e => {console.log(`观察到 ${e.name}`);});this.setState({[文件夹]:文件夹事件$});});}使成为() {const 文件夹 = Object.keys(this.state);console.log(`App 渲染 ${folders.length} 文件夹`);返回 (<div>{folders.map(folder => (<div 键={文件夹}>{文件夹}

))}

);}}ReactDOM.render(<应用程序/>,document.getElementById('app'));

<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><身体><div id="应用程序"></div>

代码段 2:按钮触发器

class App extends React.Component {构造函数(){极好的();this.events$ = new Rx.Subject();this.eventsByName$ = this.events$.groupBy(e => e.name);this.state = {};}componentDidMount() {this.eventsByName$.subscribe(folderEvents$ => {const 文件夹 = folderEvents$.key;console.log(`已创建${folder}"的新流`);folderEvents$.subscribe(e => {console.log(`观察到 ${e.name}`);});this.setState({[文件夹]:文件夹事件$});});}onClick = () =>{console.log('发射事件');this.events$.next({类型:'ADD_FOLDER',名称:'js',权限:400});};使成为() {const 文件夹 = Object.keys(this.state);console.log(`App 渲染 ${folders.length} 文件夹`);返回 (<div><button onClick={this.onClick}>添加事件<div>{folders.map(folder => (<div 键={文件夹}>{文件夹}

))}

);}}ReactDOM.render(<应用程序/>,document.getElementById('app'));

<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><身体><div id="应用程序"></div>

解决方案

它们以不同的顺序运行,因为 React 尝试将 setState() 调用批处理在一起,因此调用 setState() 不会导致组件同步重新渲染,而是等待事件回调返回.

然而,只有当且仅当您对 setState 的调用是 React 驱动事件的结果时,它才会这样做,例如 onClick 是.当您使用 setTimeout 时,React(当前)无法知道您何时完成,因此无法将它们一起批处理.相反,它会立即同步重新渲染.

据我所知,React 文档只是间接提及了这种行为:

<块引用>

setState() 不会立即改变 this.state 而是创建一个待处理的状态转换.调用 this 后访问 this.state方法可能会返回现有值.

不保证调用setState的同步操作并且可以对调用进行批处理以提高性能.

https://facebook.github.io/react/docs/react-component.html#setstate

如果你想让 React 对事物进行批处理,你需要将你的回调代码包装在 ReactDOM.unstable_batchedUpdates 中,顾名思义,它不是一个稳定的 API,所以它可以(并且可能会)改变没有警告.

setTimeout(() => {ReactDOM.unstable_batchedUpdates(() => {console.log('发射事件');this.events$.next({类型:'ADD_FOLDER',名称:'js',权限:400});});}, 1000);

理想情况下,您的代码的结构应与顺序无关.

Consider the following, almost identical, two snippets.

The difference is:

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>

解决方案

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.

这篇关于为什么按钮点击触发器与 setTimeout() 触发器不同?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
其他开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆