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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。