Javascript React.js 关于从组件监听窗口事件的最佳实践

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/32896624/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 14:20:20  来源:igfitidea点击:

React.js best practice regarding listening to window events from components

javascriptreactjs

提问by Joe C

I am animating several React.js components based on their position in the viewport. If the component is in the viewport, animate the opacity to 1, if it's not in the viewport, animate its opacity to 0. I am using getBoundingClient()'s topand bottomproperties to determine if the component is within the viewport.

我正在根据它们在视口中的位置为几个 React.js 组件设置动画。如果组件在视口中,则将不透明度设置为 1,如果它不在视口中,则将其不透明度设置为 0。我使用getBoundingClient()'stopbottomproperties 来确定组件是否在视口内。

ComponentA shows the pattern I followed for the other B, C, and D components. They each are listening for the windowscroll event.

ComponentA 显示了我对其他 B、C 和 D 组件所遵循的模式。他们每个人都在监听window滚动事件。

Is this the "React" way to do this by each component having its having to add an event listener to the window? Multiple scroll event listeners on the same window?

这是通过每个组件必须向window. 同一窗口上的多个滚动事件侦听器?

Or is there a better way by adding the scroll event listener to the window once at the Homeowner component? Then would the ownee child components still be able to know where they are in the DOM using the getBoundingClient()?

或者是否有更好的方法通过在Home所有者组件处将滚动事件侦听器添加到窗口一次?那么拥有者的子组件是否仍然能够使用getBoundingClient()?

Home = React.createClass({
 render: function() {
    <div>
       <ComponentA />
       <ComponentB />
       <ComponentC />
       <ComponentD />
    </div>
  };
});

ComponentA = React.createClass({
  componentDidMount: function() {
   window.addEventListener('scroll', this.handleScroll);
},
  componentWillUnmount: function() {
    window.removeEventListener('scroll', this.handleScroll);
   },

handleScroll: function() {
  var domElement = this.refs.domElement.getDOMNode();
  this.inViewPort(domElement);
},

inViewPort: function(element) {
  var elementBounds = element.getBoundingClientRect();
  (elementBounds.top <= 769 && elementBounds.bottom >= 430) ? TweenMax.to(element, 1.5, { opacity: 1 }) : TweenMax.to(element, 1.5, { opacity: 0 });
},
render: function() {
  return (/* html to render */);
 }

});

采纳答案by sarink

There are a few different ways you could do this. One is through composition:

有几种不同的方法可以做到这一点。一种是通过组合:

var React = require("react");
var _ = require("underscore");

var ScrollWrapper = React.createClass({
    propTypes: {
        onWindowScroll: React.PropTypes.func
    },

    handleScroll: function(event) {
        // Do something generic, if you have to
        console.log("ScrollWrapper's handleScroll");

        // Call the passed-in prop
        if (this.props.onWindowScroll) this.props.onWindowScroll(event);
    },

    render: function () {
        return this.props.children;
    },

    componentDidMount: function() {
        if (this.props.onWindowScroll) window.addEventListener("scroll", this.handleScroll);
    },

    componentWillUnmount: function() {
        if (this.props.onWindowScroll) window.removeEventListener("scroll", this.handleScroll);
    }
});

var ComponentA = React.createClass({
    handleScroll: function(event) {
        console.log("ComponentA's handleScroll");
    },

    render: function() {
        return (
            <ScrollWrapper onWindowScroll={this.handleScroll}>
                <div>whatever</div>
            </ScrollWrapper>
        );
    }
});

Now, you can place your generic logic in the ScrollWrappercomponent, and suddenly it becomes reusable. You could create a ComponentBthat renders a ScrollWrapperjust like ComponentAdoes.

现在,您可以将通用逻辑放在ScrollWrapper组件中,然后它突然变得可以重用了。你可以创建一个ComponentB渲染 aScrollWrapper就像ComponentA做的一样。

To satisfy your example, maybe you'll have to pass the ScrollWrappersome extra props from ComponentA. Maybe you'll pass it a prop that contains an instance of the refto call your logic on. You could even pass it some options or arguments to customize the tween or the bounds. I didn't code any of this because I think you'll understand it and be able to customize/write it for yourself with the base I've provided.

为了满足您的示例,也许您必须ScrollWrapperComponentA. 也许你会传递一个包含 的实例ref来调用你的逻辑的道具。您甚至可以向它传递一些选项或参数来自定义补间或边界。我没有编写任何代码,因为我认为您会理解它并能够使用我提供的基础为自己定制/编写它。

The other way to achieve this sort of thing is through a Mixin. Although, there's a lot of talk about if Mixins are good or bad, and they might even be being deprecated by React in the future? You can do some reading about this and decide for yourself what you think.

实现这种事情的另一种方法是通过 Mixin。虽然,有很多关于 Mixin 是好是坏的讨论,它们甚至可能在未来被 React 弃用?您可以阅读有关此内容并自行决定您的想法。

回答by MCSLI

Here is a simpler code snippet that should work as required. You are missing the thisbinding, as such, when you execute window.addEventListener('scroll', this.handleScroll);you are actually pointing thisto the window object.

这是一个更简单的代码片段,应该可以按要求工作。您缺少this绑定,因此,当您执行时,window.addEventListener('scroll', this.handleScroll);您实际上是在指向thiswindow 对象。

Instead you will need to bind this in the constructor. Hope it

相反,您需要在构造函数中绑定 this。希望它

class Home extends Component {
  constructor(props) {
    super(props)
    this.handleScroll = this.handleScroll.bind(this);
  }
  componentDidMount() {
    window.addEventListener('scroll', this.handleScroll);
  }
  componentWillUnmount() {
    window.removeEventListener('scroll', this.handleScroll);
  }
  handleScroll(e) {
    console.log('scroll event');
    console.log(e);
  }

  render() {
    return (
     <div>
       <ComponentA />
       <ComponentB />
       <ComponentC />
       <ComponentD />
     </div>
    );
  }
}

Another option is the below, both options should work :)

另一个选项如下,这两个选项都应该有效:)

class Home extends Component {
  componentDidMount() {
    window.addEventListener('scroll', this.handleScroll.bind(this));
  }
  componentWillUnmount() {
    window.removeEventListener('scroll', this.handleScroll.bind(this));
  }
  handleScroll(e) {
    console.log('scroll event');
    console.log(e);
  }

  render() {
    return (
     <div>
       <ComponentA />
       <ComponentB />
       <ComponentC />
       <ComponentD />
     </div>
    );
  }
}

回答by mtyson

Here it is in functional style with the useEffect hook:

这是带有 useEffect 钩子的函数式风格:

  let onScroll = (event) => {
    console.log("scrolling");
  }
  useEffect(() => {
    if (window){
      window.addEventListener('scroll', onScroll);
    }
    return () => {
      window.removeEventListener('scroll', onScroll);
    }
  }, []);

回答by Eric

I'd definitely add one event listener / component. The ideology is to have separated components that can be reused and placed "anywhere" in the application - to minimize code redundancy.

我肯定会添加一个事件侦听器/组件。其理念是拥有可以重复使用的分离组件,并可以将其放置在应用程序中的“任何地方” - 以最大限度地减少代码冗余。

Your approach to keep an event listner per compnenent is therefore valid.

因此,您为每个组件保留一个事件监听器的方法是有效的。