简体   繁体   English

为什么React会一遍又一遍地以不变的状态渲染我的组件?

[英]Why is React rendering my component over and over with an unchanged state?

I'm making a simple scroll-to-top component and I thought that React will only re-render a component if something in it changes. 我正在制作一个简单的从上到下的组件,我认为React仅在组件发生更改时才会重新渲染该组件。 Since I have a conditional tied to state in my render, shouldn't React only render it if the state changes? 由于我的渲染中有条件绑定到状态,因此React是否应该仅在状态更改时才渲染它? Instead, I'm seeing it re-render with every little scroll. 取而代之的是,我看到它随着每个小滚动都重新呈现。

Also, if I left it as-is, are there any downsides to it re-rendering so much? 另外,如果我原样保留它,那么重新渲染是否有任何负面影响?

import React from 'react';
import './scroll-to-top.css';

export default class extends React.Component {

  state = {
    shouldShowButton: false
  }

  componentDidMount() {
    window.addEventListener('scroll', this.handleScroll);
  }

  componentWillUnmount() {
    window.removeEventListener('scroll', this.handleScroll);
  }

  handleScroll = () => {
    this.setState({
    shouldShowButton: window.scrollY > 250 ? true : false
  });
 }

  render () {
    {console.log("i have rendered!")}
    return (
      this.state.shouldShowButton ? <a className="scroll-to-top" href="#">Return to Top</a> : null
    );
  };
};

Welcome to Stack Overflow :) 欢迎使用Stack Overflow :)

Let's think through your code. 让我们仔细考虑一下您的代码。

When the component loads, you're attaching a listener to the scroll event: 加载组件时,您会将侦听器附加到滚动事件:

componentDidMount() {
  window.addEventListener('scroll', this.handleScroll);
}

This fires handleScroll when the user scrolls. 用户滚动时将触发handleScroll handleScroll sets the state of the component, regardless of whether or not the ternary condition resolves as true or false: handleScroll设置组件的状态,而不考虑三元条件是否解析为true或false:

handleScroll = () => {
  this.setState({
    shouldShowButton: window.scrollY > 250 ? true : false
  });
}

Whenever we use setState , React triggers render . 每当我们使用setState ,React都会触发render Hence, render is triggering with every little scroll. 因此, render会在每次滚动时触发。

Downsides - you should be really careful of attaching anything to scroll , as it can affect performance. 缺点-您应该非常小心地附加任何scroll ,因为它会影响性能。 You might consider debouncing the event if you really, really need to do so. 如果确实需要,可以考虑取消事件的弹跳。 (Where debouncing is the technique of rate-limiting how many times a function can be called.) (去抖动是限制函数可调用次数的技术。)

This happens, because you are calling handleScroll function every time scroll event is fired. 发生这种情况的原因是,每次滚动事件触发时,您都在调用handleScroll函数。 To fix this, setState only in condition: 要解决此问题,请仅在以下情况下使用setState:

import React from 'react';
import './scroll-to-top.css';

export default class extends React.Component {

  state = {
    shouldShowButton: false
  }

  componentDidMount() {
    window.addEventListener('scroll', this.handleScroll);
  }

  componentWillUnmount() {
    window.removeEventListener('scroll', this.handleScroll);
  }

  handleScroll = () => {
    const {shouldShowButton} = this.state;
    if (!shouldShowButton && window.scrollY > 250) {
       this.setState({
          shouldShowButton: true
       });
    } else if (shouldShowButton && window.scrollY <= 250) {
       this.setState({
          shouldShowButton: false
       });
    }
 }

  render () {
    {console.log("i have rendered!")}
    return (
      this.state.shouldShowButton ? <a className="scroll-to-top" href="#">Return to Top</a> : null
    );
  };
};

No, it's typical for Component. 不,这是典型的组件。 It's re-rendered(not in DOM but in virtual DOM) each time .setState is called, props are changes or parent element is re-rendered . 每次调用.setState ,更改props重新渲染父元素时,都会重新渲染(不是在DOM中,而是在虚拟DOM中)。 Just an example how re-rendering parent also fires re-rendering for child: 仅举一个例子,父母重新渲染还可以激发孩子的重新渲染:

import React from "react";
import ReactDOM from "react-dom";

class Child extends React.Component {
  render() {
    console.log('child re-rendered');
    return 'test';
  }
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {a: 1};
    setInterval(() => this.setState(oldState => ({...oldState, a: oldState.a + 1})), 1000);
  }

  render() {
    return (
      <div className="App">
      <Child />
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Here you can check that child is re-rendered in the line as parent's .setState is called. 在这里,您可以检查是否在调用父级的.setState的行中重新渲染了子.setState

But it is not 100% to be issue for performance. 但这并不是100%的性能问题。 Virtual DOM is much faster than browser DOM. 虚拟DOM比浏览器DOM快得多。

But if you want to avoid such a behavior you may use React.PureComponent instead of React.Component and then it will not be re-rendered on parent's update. 但是,如果要避免这种行为,可以使用React.PureComponent而不是React.Component,这样它将不会在父级更新时重新呈现。 Also PureComponent handles case when .setState does not actually changes value. .setState实际上不更改值时, .setState也可以处理这种情况。 So there will be less re-rendering. 因此,重新渲染将更少。

Official docs are good enough but here is also fine article at Medium 官方文档已经足够好了,但是在Medium上不错

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM