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一下就好了。真正做到了哪里要用粘哪里。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342