React实践:回到顶部按钮

本文转载自我的个人博客

React中一个重要理念就是组件化,通过组件化从而实现组件的复用。我认为用回到顶部按钮来讲解react的组件化思想是最合适不过的了,因为回到顶部按钮其本身的实现逻辑并不复杂,但是将其组件化却用到了很多基础但是很重要的react知识点,如JSX中回调函数的this绑定、条件渲染、生命周期函数等等。

先看一下我们要实现的回到顶部组件长什么样子吧:

hui-dao-ding-bu-1.gif

注意这个实现有两个细节:

  1. 组件一开始是隐藏,只有当下滑到一定程度时才会出现。
  2. 回到顶部这个动作不是瞬间的,是有动画的。

完整的代码如下,注释里有一些简短的解释,然后我们把重要的知识点单拎出来讲解。

  import React, { Component } from 'react';
  //引入由styled-components创建的样式组件
  import {ScrollToTopWrapper} from './style';

  class ScrollToTop extends Component {
    constructor(props) {
      super(props)

      //show为true时回到顶部按钮显示,false时隐藏
      this.state = ({
        show: false
      })
      //将函数里的this指向绑定到当前组件,也就是组件ScrollToTop
      this.changeScrollTopShow = this.changeScrollTopShow.bind(this);
      this.scrollToTop = this.scrollToTop.bind(this);
    }
    //挂载事件监听
    componentDidMount() {
      window.addEventListener('scroll', this.changeScrollTopShow)
    }
    //卸载事件监听
    componentWillUnmount() {
      window.removeEventListener('scroll', this.changeScrollTopShow)
    }
    render() {
      const { show } = this.state;
      return(
        //ScrollToTopWrapper是一个由styled-components定义的样式组件,其本质
        //是一个div标签
        <ScrollToTopWrapper>
          {
            // 逻辑与符号左边的show为true时返回右边的html标签
            show && 
            <div 
              className = "scrollTop" 
              onClick = {this.scrollToTop}
            >
              <span className ="iconfont icon-dingbu"></span>
            </div>
          }
        </ScrollToTopWrapper>

      )
    }
    //控制show的状态从而控制回到顶部按钮的显示和隐藏
    changeScrollTopShow() {
      if (window.pageYOffset < 400) {
        this.setState({
          show: false
        })
      }else {
        this.setState({
          show: true
        })
      }
    }
    //添加动画效果
    scrollToTop() {
      const scrollToTop = window.setInterval(() => {
        let pos = window.pageYOffset;
        if ( pos > 0 ) {
          window.scrollTo( 0, pos - 20 );
        } else {
          window.clearInterval( scrollToTop );
        }
      }, 1);
    }
  }

  //导出组件
  export default ScrollToTop;

知识点一:回调函数中this的指向问题

我们可以注意到,上段代码中我们对两个函数changeScrollTopShowscrollToTop进行了this绑定。我们先分别看一下如果这两个函数不绑定this会是什么样子。

对于changeScrollTopShow函数而言,如果我们不对其绑定this,那么changeScrollTopShow函数内部的this指向的则是调用addEventListener的对象,即windows。这个时候就会出现问题了,因为在changeScrollTopShow函数里我们调用了this.setState方法,而显然windows对象里并没有这个方法,所以程序就会报错。所以我们需要对changeScrollTopShow函数的this进行绑定,绑定为ScrollToTop组件本身,因为setState方法在这个组件对象里面。

再来看看scrollToTop函数。scrollToTop函数是JSX中的回调函数,如果我们不对它的this进行绑定,则scrollToTop函数里的this指向undifined。可能有心的同学已经注意到了,就我们这个例子来说,scrollToTop函数其实是没有必要进行this绑定的,因为在scrollToTop函数内部我们并没有用到this。但是我建议大家在定义的时候就绑定一下,因为在函数没有写完的情况下你往往是不知道函数内部会不会用到this的。

关于如何绑定thisreact官方文档给了三种方法,一种是利用上例中bind方法,还有两种方法是使用箭头函数或使用属性初始化语法器。

changeScrollTopShow函数为例,使用箭头函数处理this

  componentDidMount() {
    window.addEventListener('scroll', (e) => this.changeScrollTopShow(e) )
  }

箭头函数之所以能起到绑定this的作用是因为箭头函数体内的this对象就是定义时所在 的对象,而不是使用时所在的对象,而且箭头函数中的this指向是固定的,不会变的。在我们这个例子中,箭头函数是定义在ScrollToTop这个对象中的(注意组件的本质是对象)所以箭头函数体内的this永远都会指向ScrollToTop这个组件。

changeScrollTopShow函数为例,使用属性初始化语法器处理this

  changeScrollTopShow = () => {
    if (window.pageYOffset < 400) {
      this.setState({
        show: false
      })
    }else {
      this.setState({
        show: true
      })
    }
  }

属性初始化语法器是在定义函数时做文章,而调用的时的方法是不变的。

知识点二:生命周期函数

在这个例子中我们用到了两个生命周期函数:componentDidMount()componentWillUnmount()

  //挂载事件监听
  componentDidMount() {
    window.addEventListener('scroll', this.changeScrollTopShow)

  }
  //卸载事件监听
  componentWillUnmount() {
    window.removeEventListener('scroll', this.changeScrollTopShow)
  }

componentDidMount()好理解,当我们的ScrollToTop组件被渲染到DOM后会执行componentDidMount()钩子,所以我们把时间监听函数放在这个钩子上,表示当组件被渲染时开始监听事件。合情合理,这一步是必须的。

但是有的同学可能会有疑问,那在componentWillUnmount()里卸载事件监听函数也是必须的吗?
答案是,是必须的,是必须的,是必须的。

你可以自己实践一下,如果把卸载监听事件那一段注释掉,当ScrollToTop组件被unmount时(通常我们有两种方式会导致一个组件unmount,一是通过条件渲染,二是通过某些路由手段路由到了别的组件。)你就会得到一个警告(Warning):

  • Warning: Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op.

这个Warning的意思是你在一个已经被卸载的组件是上使用了setState, replaceState, or forceUpdate方法。 我们真的使用了吗,回查一下代码我们发现,在changeScrollTopShow这个方法里我们果然使用了setState。因为监听事件被挂载上去以后,我们并没有卸载它,导致ScrollToTop组件被卸载(unmount)时我们的changeScrollTopShow依然在运行,而changeScrollTopShow里又正好调用了setState方法,但是此时组件已经被卸载了,被卸载的组件的state是不能再更新的,所以这个时候会就出现警告。

关于这个警告我有一篇解释的更详细的文章,感兴趣的话请移步

知识点三:条件渲染

ScrollToTop这个组件中我们通过show这个变量的状态,即truefalse来控制组件的显示和隐藏。实现方法是:

  {
    // 逻辑与符号左边的show为true时返回右边的html标签
    show && 
    <div 
      className = "scrollTop" 
      onClick = {this.scrollToTop}
    >
      <span className ="iconfont icon-dingbu"></span>
    </div>
  }

这里是想提醒一下大家两点:

  1. JSX中可以使用js,但是记得把js用花括号括起来。
  2. 逻辑与符号&&符号的一些扩展用法:逻辑与操作可以用于任何对象,不仅仅是布尔值。在有一个操作数不是布尔值的情况下,逻辑与返回的不一定是布尔值。比如在我们这个例子中,第一个操作数为布尔值,第二个操作数为对象,那么在第一个操作数为true的情况下返回第二个操作数。这样我们就避免了if语法,代码看起来更加整洁。

小结

由于ScrollToTop这个组件不需要用到别的组件的参数,所以复用的时候也不涉及传参的问题,可以直接把自己提供给任何别的组件,不论嵌套有多深,都只需要import一下就好了。真正做到了哪里要用粘哪里。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。