likes
comments
collection
share

(14)首页开发——⑥ “返回顶部”功能实现 | React.js 项目实战:PC 端“简书”开发

作者站长头像
站长
· 阅读数 4
转载请注明出处,未经同意,不可修改文章内容。

🔥🔥🔥“前端一万小时”两大明星专栏——“从零基础到轻松就业”、“前端面试刷题”,已于本月大改版,合二为一,干货满满,欢迎点击公众号菜单栏各模块了解。

1 需求

❓查看“简书”官网,并实现需求:

  1. 当点击“回到顶部”按钮时,页面立刻回到顶部;
  2. “回到顶部”按钮一开始是“隐藏”状态,当页面向下滑动一定值的时候,“回到顶部”按钮出现(反之亦然)。

(14)首页开发——⑥ “返回顶部”功能实现 | React.js 项目实战:PC 端“简书”开发

✔️需求分析:

  1. 单就“回到顶部”这个功能而言,一行代码就可以搞定——调用 window.scrollTo(0, 0) 方法;
  2. 但“回到顶部”在适当的时机“隐藏”和“显示”则需要走 React-redux“数据”改变的流程,稍微麻烦点,但还是“套路”,所以不可怕。

2 编写“回到顶部”逻辑代码

🔗前置知识: 《JavaScript 基础——浏览器提供的对象:① BOM》——掌握“常用的 window 对象属性”; 《JavaScript 基础——浏览器提供的对象:② DOM》——掌握“ document.documentElement.scrollTop 的用法”; 《JavaScript 基础——JS 事件:③ 常见事件使用》——掌握“window 事件”

1️⃣打开 home 目录下的 index.js 文件,我们先实现简单的“回到顶部”功能:

import React, {Component} from "react";

import Content from "./components/Content";
import Label from "./components/Label";
import Panels from "./components/Panels";
import Download from "./components/Download";

import {
  Section,
  Aside,
  Main,
  ToTop
  
} from "./style.js";

import { connect } from "react-redux";

import {actionCreators} from "./store"; 


class Home extends Component {
  
  // 1️⃣-②:我们直接将 handleScrollTop 事件方法写在这里;
  handleScrollTop() {
    window.scrollTo(0, 0);
  }
  
  render() {
    return( 
      <div>
        <Section className="layout clearfix"> 
          <Aside>
            <Panels />
            <Download />
          </Aside>
        
          <Main>
            <img className="banner-img" src="https://qdywxs.github.io/jianshu-images/carousel01.jpg" alt="" />
            
            <Label />
            <Content />
          </Main>
        </Section>
        
      
        {/* ❗️1️⃣-①:给 ToTop 样式组件绑定一个 click 点击事件; */}
        <ToTop onClick={this.handleScrollTop}>  
          <span className="up">^</span>
          <span className="tooltip">回到顶部</span>
        </ToTop>
      </div>
    )
  }

  componentDidMount() { 
    this.props.changeHomeData();
  }
  
}

const mapDispatchToProps = (dispatch) => { 
  return {
    changeHomeData() { 
      
      const action = actionCreators.getHomeInfo();
  
      dispatch(action)
    
    },
  }
}

export default connect(null, mapDispatchToProps)(Home); 

返回页面查看(成功回到顶部): (14)首页开发——⑥ “返回顶部”功能实现 | React.js 项目实战:PC 端“简书”开发

2️⃣继续去实现“在适当时机”显示和隐藏“回到顶部”按钮,打开 home 目录下 store 中的 reducer.js 文件,在其中定义一个变量 showToTop ,用其来保存“样式组件 ToTop ”的“显示”和“隐藏”:

import {fromJS} from "immutable"; 

import {INIT_HOME_DATA, ADD_HOME_DATA} from "./actionTypes";

const defaultState = fromJS({
  labelList: [],
  articleList: [],
  panelsList: [],
  articlePage: 1,
  
  // ❗️2️⃣-①:定义一个变量,用以保存 ToTop 的“显示”和“隐藏”;
  showToTop: false // ❗️❗️❗️初始为“不显示”!
  
})

export default (state=defaultState, action) => {  
  if(action.type === INIT_HOME_DATA) {
    return state.merge({  
      labelList: action.labelList,
      articleList: action.articleList,
      panelsList: action.panelsList
    })
  }
  
  if(action.type === ADD_HOME_DATA) {
    return state.merge({
      "articleList": state.get("articleList").concat(action.moreArticleList),
      "articlePage": action.nextPage
    })
  }
  
  return state;
}

2️⃣-②:返回 home 目录下的 index.js 文件;

import React, {Component} from "react";

import Content from "./components/Content";
import Label from "./components/Label";
import Panels from "./components/Panels";
import Download from "./components/Download";

import {
  Section,
  Aside,
  Main,
  ToTop
  
} from "./style.js";

import { connect } from "react-redux";

import {actionCreators} from "./store"; 


class Home extends Component {
  
  handleScrollTop() {
    window.scrollTo(0, 0);
  }
  
  render() {
    return( 
      <div>
        <Section className="layout clearfix"> 
          <Aside>
            <Panels />
            <Download />
          </Aside>
        
          <Main>
            <img className="banner-img" src="https://qdywxs.github.io/jianshu-images/carousel01.jpg" alt="" />
            
            <Label />
            <Content />
          </Main>
        </Section>
        
        {/* 2️⃣-⑤:然后,我们在这里来编写“显示”和“隐藏”的逻辑——用“三元运算符”来作判断; */}
        {this.props.showToTop ?
          <ToTop onClick={this.handleScrollTop}>  
            <span className="up">^</span>
            <span className="tooltip">回到顶部</span>
          </ToTop>
          :null
        }
      </div>
    )
  }

  componentDidMount() { 
    this.props.changeHomeData();
  }
  
}

// 2️⃣-④:定义 mapStateToProps:
const mapStateToProps = (state) => ({
  showToTop: state.getIn(["home", "showToTop"])
})


const mapDispatchToProps = (dispatch) => { 
  return {
    changeHomeData() { 
      
      const action = actionCreators.getHomeInfo();
  
      dispatch(action)
    
    },
  }
}

/*
2️⃣-③:既然 reducer 中新增了“数据”,且本组件会用到这个“数据”,
故,给 connect 传递另一个参数 mapStateToProps,进而去获取到这个“数据”;
注释掉下面这行代码,重新编写~
export default connect(null, mapDispatchToProps)(Home); 
 */
export default connect(mapStateToProps, mapDispatchToProps)(Home)

返回页面查看(由于初始为 false ,所以一开始“返回顶部”是不显示的。但,当我们将 showToTop 的值改为 true 时,“返回顶部”又显示出来了): (14)首页开发——⑥ “返回顶部”功能实现 | React.js 项目实战:PC 端“简书”开发

✔️由上边的视频可见,控制“返回顶部”的“显示”和“隐藏”,其实就是控制 showToTop 这个“数据”的值( true / false )!

3️⃣返回 home 目录下的 index.js 文件:

import React, {Component} from "react";

import Content from "./components/Content";
import Label from "./components/Label";
import Panels from "./components/Panels";
import Download from "./components/Download";

import {
  Section,
  Aside,
  Main,
  ToTop
  
} from "./style.js";

import { connect } from "react-redux";

import {actionCreators} from "./store"; 


class Home extends Component {
  
  handleScrollTop() {
    window.scrollTo(0, 0);
  }
  
  render() {
    return( 
      <div>
        <Section className="layout clearfix"> 
          <Aside>
            <Panels />
            <Download />
          </Aside>
        
          <Main>
            <img className="banner-img" src="https://qdywxs.github.io/jianshu-images/carousel01.jpg" alt="" />
            
            <Label />
            <Content />
          </Main>
        </Section>
        
        {this.props.showToTop ?
          <ToTop onClick={this.handleScrollTop}>  
            <span className="up">^</span>
            <span className="tooltip">回到顶部</span>
          </ToTop>
          :null
        }
      </div>
    )
  }

  componentDidMount() { 
    this.props.changeHomeData();
    
    // ❗️3️⃣-①:我们在生命周期函数 componentDidMount 里绑定一些事件;
    this.bindEvent();
  }
  
	// 3️⃣-②:具体的事件方法写在这里;
	bindEvent() {
    // ❗️监听 window 的 scroll 事件!
    // ❓3️⃣-③:但具体怎么调用呢?
    window.addEventListener("scroll", this.props.changeShowToTop) /*
    																														3️⃣-⑤:因此这里
    																														通过 this.props 来
                                                                调用 changeToTopShow
                                                                方法;
                                                                   */
  }

}

const mapStateToProps = (state) => ({
  showToTop: state.getIn(["home", "showToTop"])
})


const mapDispatchToProps = (dispatch) => ({ 
  changeHomeData() { 
    const action = actionCreators.getHomeInfo();
    dispatch(action)
  },
  
  // 3️⃣-④:定义 scroll 这个“用户行为”会被传递给 reducer;
  changeShowToTop() {
    
    /*
    3️⃣-⑥:用 document.documentElement.scrollTo 来作条件判断,
    当滚动到一定量的时候,进行“数据”的改变;
     */
  	if(document.documentElement.scrollTop > 120) {
      const action = actionCreators.changeShowToTopAction(true); // ❗️传递一个参数 true!
      dispatch(action)
    }else {
      const action = actionCreators.changeShowToTopAction(false); // ❗️传递一个参数 false!
      dispatch(action)
    }
  }
})


export default connect(mapStateToProps, mapDispatchToProps)(Home)

3️⃣-⑦:请记得去 home 目录下 store 中的 actionTypes.jsactionCreators.js 文件中分别定义“常量”和 action;

  • actionTypes.js
export const INIT_HOME_DATA = "init_home_data";  
export const ADD_HOME_DATA ="add_home_data";

// ❗️定义好常量~
export const CHANGE_SHOW_TO_TOP="change_show_to_top";
  • actionCreators.js
import axios from "axios";

// ❗️❗️❗️先引入“常量”!
import {INIT_HOME_DATA, ADD_HOME_DATA, CHANGE_SHOW_TO_TOP} from "./actionTypes";  

import {fromJS} from "immutable";


const initHomeData = (result) => ({
  type: INIT_HOME_DATA, 
 
  labelList: fromJS(result.labelList),
  articleList: fromJS(result.articleList),
  panelsList: fromJS(result.panelsList)
});

export const getHomeInfo = () => {
  return(dispatch) => {
    axios.get("/api/homeData.json")
      .then((res) => {
        const result = res.data.data;
      
        const action = initHomeData(result);
        dispatch(action); 
      })
      .catch(() => {alert("error")})
  }
}

const addHomeData = (result, nextPage) => ({ 
  type: ADD_HOME_DATA,
  moreArticleList: fromJS(result.moreArticleList),
  nextPage
})

export const getMoreList = (page) => { 
  return(dispatch) => {
    axios.get("/api/homeList.json?page=" + page)
    
      .then((res) => {
        const result = res.data.data;
        
        const action = addHomeData(result, page + 1);
        dispatch(action); 
      })
      .catch(() => {alert("error")})
  }
}


// 3️⃣-⑧:定义 action;
export const changeShowToTopAction = (show) => ({
  type: CHANGE_SHOW_TO_TOP,
  show
})

3️⃣-⑨:打开 home 目录下 store 中的 reducer.js 文件,编写修改数据的逻辑;

import {fromJS} from "immutable"; 

// ❗️❗️❗️先引入“常量”!
import {INIT_HOME_DATA, ADD_HOME_DATA, CHANGE_SHOW_TO_TOP} from "./actionTypes";

const defaultState = fromJS({
  labelList: [],
  articleList: [],
  panelsList: [],
  articlePage: 1,
  
  showToTop: false // ❗️❗️❗️初始为“不显示”!
  
})

export default (state=defaultState, action) => {  
  if(action.type === INIT_HOME_DATA) {
    return state.merge({  
      labelList: action.labelList,
      articleList: action.articleList,
      panelsList: action.panelsList
    })
  };
  
  if(action.type === ADD_HOME_DATA) {
    return state.merge({
      "articleList": state.get("articleList").concat(action.moreArticleList),
      "articlePage": action.nextPage
    })
  };
  
  // 3️⃣-⑩:当reducer 接收到 action 后,需要告诉 store 怎样“改变数据”;
  if(action.type === CHANGE_SHOW_TO_TOP) {
    return state.set("showToTop", action.show);
  }
  
  return state;
};

返回页面查看(成功实现在“适当的时机”去“显示”和“隐藏”): (14)首页开发——⑥ “返回顶部”功能实现 | React.js 项目实战:PC 端“简书”开发

3 移除 scroll 事件

❗️以上我们虽然实现了本篇一开始提的“需求”,但还有一个点一定得注意:

4️⃣回到 home 目录下的 index.js 文件:

import React, {Component} from "react";

import Content from "./components/Content";
import Label from "./components/Label";
import Panels from "./components/Panels";
import Download from "./components/Download";

import {
  Section,
  Aside,
  Main,
  ToTop
  
} from "./style.js";

import { connect } from "react-redux";

import {actionCreators} from "./store"; 


class Home extends Component {
  
  handleScrollTop() {
    window.scrollTo(0, 0);
  }
  
  render() {
    return( 
      <div>
        <Section className="layout clearfix"> 
          <Aside>
            <Panels />
            <Download />
          </Aside>
        
          <Main>
            <img className="banner-img" src="https://qdywxs.github.io/jianshu-images/carousel01.jpg" alt="" />
            
            <Label />
            <Content />
          </Main>
        </Section>
        
        {this.props.showToTop ?
          <ToTop onClick={this.handleScrollTop}>  
            <span className="up">^</span>
            <span className="tooltip">回到顶部</span>
          </ToTop>
          :null
        }
      </div>
    )
  }

  componentDidMount() { 
    this.props.changeHomeData();
    
    // ❗️4️⃣-①:当组件“挂载”好后,我们在这里绑定了一些事件;
    this.bindEvent();
  }

  // ❗️4️⃣-③:所以,我们在生命周期函数 componentWillUnmount 中“解绑”scroll 事件!
	componentWillUnmount() {
    window.removeEventListener("scroll", this.props.changeShowToTop)
  }

  bindEvent() {
    
    /*
    ❗️❗️❗️4️⃣-②:且,这些“事件”是绑定在“全局”的 window 对象上的!故,我们需要做的是——
    当“组件”被从页面上移除的时候,我们一定得把 scroll 事件从 window 上“解绑”。
    否则,这个“组件”的事件就会影响其他的“组件”!
     */
    window.addEventListener("scroll", this.props.changeShowToTop) 
  }

}

const mapStateToProps = (state) => ({
  showToTop: state.getIn(["home", "showToTop"])
})


const mapDispatchToProps = (dispatch) => ({ 
  changeHomeData() { 
    const action = actionCreators.getHomeInfo();
    dispatch(action)
  },
  
  changeShowToTop() {
    if(document.documentElement.scrollTop > 120) {
      const action = actionCreators.changeShowToTopAction(true); 
      dispatch(action)
    }else {
      const action = actionCreators.changeShowToTopAction(false);
      dispatch(action)
    }
  }
})


export default connect(mapStateToProps, mapDispatchToProps)(Home)

返回页面查看,无任何异常,成功实现: (14)首页开发——⑥ “返回顶部”功能实现 | React.js 项目实战:PC 端“简书”开发

下一篇,我们开始编写“详情页”的代码,我们再去好好地走走“流程”,用用“套路”。孰能生巧,多写几次,你一定能熟练掌握 React 的使用。

祝好,qdywxs ♥ you!