本文轉載自我的個人博客。
React
中一個重要理念就是組件化,通過組件化從而實現組件的復用。我認為用回到頂部按鈕來講解react
的組件化思想是最合適不過的了,因為回到頂部按鈕其本身的實現邏輯并不復雜,但是將其組件化卻用到了很多基礎但是很重要的react
知識點,如JSX
中回調函數的this綁定、條件渲染、生命周期函數等等。
先看一下我們要實現的回到頂部組件長什么樣子吧:
注意這個實現有兩個細節:
- 組件一開始是隱藏,只有當下滑到一定程度時才會出現。
- 回到頂部這個動作不是瞬間的,是有動畫的。
完整的代碼如下,注釋里有一些簡短的解釋,然后我們把重要的知識點單拎出來講解。
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的指向問題
我們可以注意到,上段代碼中我們對兩個函數changeScrollTopShow
和scrollToTop
進行了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
的。
關于如何綁定this
,react
的官方文檔給了三種方法,一種是利用上例中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
這個變量的狀態,即true
和false
來控制組件的顯示和隱藏。實現方法是:
{
// 邏輯與符號左邊的show為true時返回右邊的html標簽
show &&
<div
className = "scrollTop"
onClick = {this.scrollToTop}
>
<span className ="iconfont icon-dingbu"></span>
</div>
}
這里是想提醒一下大家兩點:
- 在
JSX
中可以使用js
,但是記得把js
用花括號括起來。 - 邏輯與符號
&&
符號的一些擴展用法:邏輯與操作可以用于任何對象,不僅僅是布爾值。在有一個操作數不是布爾值的情況下,邏輯與返回的不一定是布爾值。比如在我們這個例子中,第一個操作數為布爾值,第二個操作數為對象,那么在第一個操作數為true
的情況下返回第二個操作數。這樣我們就避免了if
語法,代碼看起來更加整潔。
小結
由于ScrollToTop
這個組件不需要用到別的組件的參數,所以復用的時候也不涉及傳參的問題,可以直接把自己提供給任何別的組件,不論嵌套有多深,都只需要import
一下就好了。真正做到了哪里要用粘哪里。