本文转载自我的个人博客。
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
一下就好了。真正做到了哪里要用粘哪里。