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

复制代码
转载请注明出处,未经同意,不可修改文章内容。

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

1 需求

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

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

✔️需求分析:

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

2 编写"回到顶部"逻辑代码

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

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

jsx 复制代码
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); 

返回页面查看(成功回到顶部):

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

javascript 复制代码
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 文件;

javascript 复制代码
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 时,"返回顶部"又显示出来了):

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

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

jsx 复制代码
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
javascript 复制代码
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
javascript 复制代码
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 文件,编写修改数据的逻辑;

javascript 复制代码
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;
};

返回页面查看(成功实现在"适当的时机"去"显示"和"隐藏"):

3 移除 scroll 事件

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

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

jsx 复制代码
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)

返回页面查看,无任何异常,成功实现:

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

祝好,qdywxs ♥ you!

相关推荐
Boilermaker19928 分钟前
【Java EE】SpringIoC
前端·数据库·spring
中微子19 分钟前
JavaScript 防抖与节流:从原理到实践的完整指南
前端·javascript
天天向上102434 分钟前
Vue 配置打包后可编辑的变量
前端·javascript·vue.js
芬兰y1 小时前
VUE 带有搜索功能的穿梭框(简单demo)
前端·javascript·vue.js
好果不榨汁1 小时前
qiankun 路由选择不同模式如何书写不同的配置
前端·vue.js
小蜜蜂dry1 小时前
Fetch 笔记
前端·javascript
拾光拾趣录1 小时前
列表分页中的快速翻页竞态问题
前端·javascript
小old弟1 小时前
vue3,你看setup设计详解,也是个人才
前端
Lefan1 小时前
一文了解什么是Dart
前端·flutter·dart
Patrick_Wilson1 小时前
青苔漫染待客迟
前端·设计模式·架构