(07)Header 组件开发——⑦ AJAX 获取推荐数据 | React.js 项目实战:PC 端“简书”开发

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

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

1 需求分析

之前的 6 篇文章,我们算是搭建好了整个项目的架子,接下来我们就可以放开手脚开始实现交互需求了。

本篇要实现的需求为:当“聚焦”在 SearchPanel 样式组件上时,加载出 PanelLabels 样式组件里边的所有 LabelLink 数据

我们可以看下“简书”官网在这一块的效果展示(当点击 input“搜索框”时,它会发一个“请求 trending_search ”(且只在第一次点击时会发送这个“请求”),其“返回值”和 PanelLabels 里的值是一一对应的):
(07)Header 组件开发——⑦ AJAX 获取推荐数据 | React.js 项目实战:PC 端“简书”开发
所以,我们会用到 AJAX 来获取“异步数据”,并予以展示。

既然是一个中大型正式项目,我们就会用到 Redux-thunk 等“中间件”协助我们开发。

以下文字,我们就先配置 Redux-thunk,然后 mock “推荐”数据,最后获取这个 mock 的数据并展示出来!

2 安装和配置 Redux-thunk

1️⃣安装 Redux-thunk 并重新启动:
(07)Header 组件开发——⑦ AJAX 获取推荐数据 | React.js 项目实战:PC 端“简书”开发

2️⃣打开 src 目录下 store 中的 index.js 文件:

/*
2️⃣-①:从 redux 中引入 applyMiddleware 方法。
这个方法使得我们可以使用“中间件”;
 */
import { createStore, compose, applyMiddleware } from "redux";  

import reducer from "./reducer"; 

// 2️⃣-②:从 redux-thunk 库中引入 thunk 模块;
import thunk from "redux-thunk";

const composeEnhancers =

  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;



const enhancer = composeEnhancers(  

  applyMiddleware(thunk) // ❗️2️⃣-③:顺便把 thunk 通过 applyMiddleware 执行一下!

);

const store = createStore(
  reducer,
 
  enhancer 
  
  
);  

export default store; 

返回页面查看效果(页面没报错,即 Redux-thunk 安装和配置成功):
(07)Header 组件开发——⑦ AJAX 获取推荐数据 | React.js 项目实战:PC 端“简书”开发

3 mock “推荐”数据

有了 Redux-thunk,我们就可以在 action 中进行“异步”的操作了。

作为一个“前后端分离”的项目,在 AJAX 请求数据前,我们得自己 mock 一些数据辅助我们开发。

❓实际项目中应该怎样 mock 数据呢?

答:❗️假如一开始我们就和后端的伙伴约定好了此处需求的“接口”(如 /api/headerList.json ),此时我们就可以利用 Create-react-app 提供给我们的 public 目录来放置“模拟数据”的 api 文件夹。

当我们用 AJAX 请求路径为 /api/headerList.json 的数据文件时,Create-react-app 底层搭建的 Node 服务器会首先到“工程目录”下查看是否有对应的“路由”。

如果找不到(前后端联调前,肯定是找不到的),它就会去 public 目录下查找 api 目录下的 headerList.json 文件,并显示出来。

待前端整个项目开发结束,和后端进行项目联调时,那会儿后端已把真实的“数据接口”开发完毕,我们就只需要做一件事:将 public 目录下的 api 文件删除,程序自动就会去获取并显示真实的接口数据。

OK,既然知道了 mock 数据的方式,接下来我们就开始操作吧。

3️⃣-①:在项目的 public 目录下新增一个 api 文件夹,同时在文件夹下新增一个 headerList.json 文件;
(07)Header 组件开发——⑦ AJAX 获取推荐数据 | React.js 项目实战:PC 端“简书”开发

3️⃣-②:编写 mock 数据 headerList.json 中的内容;

{
  "success": true,
  "data": ["公众号 | 前端一万小时","原生 JS","Vue","React.js","JavaScript","Oli","蚂蚁金服","阿里","qdywxs","olizhao","语雀","Matplotlib","ggplot","知乎:前端一万小时","2020 前端面试","Oliver","Theano","SciPy","PyTorch","Plotly","公众号:前端一万小时","Keras","Gensim","Vue 云笔记","React 简书","掘金:itsOli","JS 入门","JS 初识","JS 进阶","OliZhao","ID:qdywxs","mock 数据","前","端","一","万","小","时","Oli-Zhao","Jobs","欢","迎","加","入","语","雀","私","有","库","加油!"]
}

4 获取并显示数据

打开 header 目录下的 index.js 文件:

import React, {Component} from "react";
import {
HeaderWrapper,
Logo,
Navbar,
ItemList,
LinkList,
SearchArea,
SearchInput,
SearchPanel,
PanelTitle,
PanelChange,
PanelLabels,
LabelLink,
Extra,
ExtraLink
} from "./style";
import { connect } from "react-redux";
import {actionCreators} from "./store";
class Header extends Component {
render() {
return (
<HeaderWrapper>
<Logo>
<img src="https://qdywxs.github.io/jianshu-images/logo.png" alt="logo" />
</Logo>
<Navbar className="clearfix">
<ItemList className="active">
<LinkList href="/">
首页
</LinkList>
</ItemList>
<ItemList>
<LinkList href="/">
下载APP
</LinkList>           
</ItemList>
</Navbar>
<SearchArea>
{/*
❗️4️⃣-①:给 SearchInput 添加一个 onfocus 事件,
当“聚焦”时,获取 AJAX 数据;
*/}
<SearchInput
onFocus={this.props.handleInputFocus}
/>
<span className="iconfont icon-search">&#xe63e;</span>
<SearchPanel>
<PanelTitle>
热门搜索
<PanelChange
onMouseDown={this.props.handleMouseDown}
onMouseUp={this.props.handleMouseUp}
> 
<span className={this.props.refresh ? "iconfont refresh" : "iconfont"}>&#xe65f;</span>
换一批
</PanelChange>
</PanelTitle>
<PanelLabels className="clearfix">
<LabelLink href="/">
区块链
</LabelLink>
<LabelLink href="/">
故事
</LabelLink>
<LabelLink href="/">
小程序
</LabelLink>
<LabelLink href="/">
前端一万小时
</LabelLink>
</PanelLabels>
</SearchPanel>
</SearchArea>
<Extra>
<span className="iconfont icon-textsize" >&#xe739;</span>
<ExtraLink className="login" href="/">
登录
</ExtraLink>
<ExtraLink className="register" href="/">
注册
</ExtraLink> 
<ExtraLink className="writing" href="/">
<span className="iconfont icon-pen">&#xe600;</span>
写文章
</ExtraLink>     
</Extra>
</HeaderWrapper>
)
}
}
const mapStateToProps = (state) => { 
return { 
refresh: state.getIn(["header", "refresh"])
}
}
const mapDispatchToProps = (dispatch) => {  
return {
handleMouseDown() { 
const action = actionCreators.changeClassNameAction(); 
dispatch(action)
},
handleMouseUp() {
const action = actionCreators.resumeClassNameAction();
dispatch(action)
}, // ❗️注意这里的 , 不能少!
/*
4️⃣-②:Redux-thunk 中,“异步”代码我们是放在 action 中进行。
这里我们仅作方法的“调用”;
*/
handleInputFocus() {
const action = actionCreators.initLabelAction();
dispatch(action)
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Header); 

4️⃣-③:打开 header 目录下 store 中的 actionCreators.js 文件,定义 action:

import {CHANGE_CLASS_NAME, RESUME_CLASS_NAME} from "./actionTypes";
export const changeClassNameAction = () => ({
type: CHANGE_CLASS_NAME
})
export const resumeClassNameAction = () => ({
type: RESUME_CLASS_NAME
})
// 4️⃣-④:在 action 中添加 AJAX“异步”代码;
export const initLabelAction = () => {
}

4️⃣-⑤:先得安装 axios,才能进一步编写“异步”函数;
(07)Header 组件开发——⑦ AJAX 获取推荐数据 | React.js 项目实战:PC 端“简书”开发

4️⃣-⑥:返回 header 目录下 store 中的 actionCreators.ja 文件,编写“异步”代码;

import {CHANGE_CLASS_NAME, RESUME_CLASS_NAME} from "./actionTypes";
// 4️⃣-⑦:引入 axios 模块;
import axios from "axios";
export const changeClassNameAction = () => ({
type: CHANGE_CLASS_NAME
})
export const resumeClassNameAction = () => ({
type: RESUME_CLASS_NAME
})
// 4️⃣-⑧:编写“异步”函数;
export const initLabelAction = () => {
return(dispatch) => {
axios.get("/api/headerList.json")
.then((res) => {
const data = res.data;
// 4️⃣-⑨:在控制台打印一下这个“数据”,看是否已获取到!
console.log(data)
})
.catch(() => {alert("error")})
}
}

返回页面控制台查看(数据的确获取到了,但最后几秒有 bug——重复点击,重复获取数据,这个 bug 稍后再解决,这里先记下):
(07)Header 组件开发——⑦ AJAX 获取推荐数据 | React.js 项目实战:PC 端“简书”开发

4️⃣-⑩:既然 AJAX 能获取到数据,我们就可以利用这些数据了。打开 header 目录下 store 中的 reducer.js 文件,添加一个新数据 list: [] 来表示 PanelLabels 样式组件中的数据项;

import {CHANGE_CLASS_NAME, RESUME_CLASS_NAME} from "./actionTypes";
import {fromJS} from "immutable";
const defaultState = fromJS({  
refresh: false,
// ❗️添加一个新数据,初始值为空,其具体实际数值为 AJAX 获取到的“数据”!
list: []
})
export default (state=defaultState, action) => {
if(action.type === CHANGE_CLASS_NAME) {
return state.set("refresh", true);     
}
if(action.type === RESUME_CLASS_NAME) {
return state.set("refresh", false);   
}
return state;
}

4️⃣-⑪:用 AJAX 获取到的数据替换上一步中初始的“空数组”。又是“修改数据”的套路,那我们继续走 Redux 的工作流程;

4️⃣-⑫:打开 header 目录下 store 中的 actionTypes.js 文件:

export const CHANGE_CLASS_NAME = "change_class_name";
export const RESUME_CLASS_NAME ="resume_class_name";
export const CHANGE_LIST="change_list"; // ❗️定义好常量~

4️⃣-⑬:打开 header 目录下 store 中的 actionCreators.js 文件,编写 action:

// 4️⃣-⑭:先引入“常量”;
import {CHANGE_CLASS_NAME, RESUME_CLASS_NAME, CHANGE_LIST} from "./actionTypes";
import axios from "axios";
// 4️⃣-⑱:引入 fromJS 方法;
import {fromJS} from "immutable";
export const changeClassNameAction = () => ({
type: CHANGE_CLASS_NAME
})
export const resumeClassNameAction = () => ({
type: RESUME_CLASS_NAME
})
// 4️⃣-⑯:在这里定义 action;
const changeListAction = (data) => ({
type: CHANGE_LIST,
/*
❗️❗️❗️4️⃣-⑰:这里请一定注意,这里的 data 是从“接口”获取到的“数组”对象,
它是一个“JS 对象”。
但在上边的第“4️⃣-⑩”步中,list 数据项被 fromJS 修改成了“immutable 对象”,
因此,这里也应该将 data 转换为“immutable 对象”!
*/
/*
4️⃣-⑲:将 data 转化为“immutable 对象”~
data: data  
*/
data: fromJS(data)
})
export const initLabelAction = () => {
return(dispatch) => {
axios.get("/api/headerList.json")
.then((res) => {
const data = res.data;
// 4️⃣-⑮:获取到数据后,需要去替换初始的空数组;
const action = changeListAction(data.data);
dispatch(action) // ❗️将这个 action 发送给 reducer!
})
.catch(() => {alert("error")})
}
}

4️⃣-⑳:打开 header 目录下 store 中的 reducer.js 文件:

// 4️⃣-㉑:先引入“常量”;
import {CHANGE_CLASS_NAME, RESUME_CLASS_NAME, CHANGE_LIST} from "./actionTypes";
import {fromJS} from "immutable";
const defaultState = fromJS({  /*❗️*/
refresh: false,
list: []
})
export default (state=defaultState, action) => {
if(action.type === CHANGE_CLASS_NAME) {
return state.set("refresh", true); 
}
if(action.type === RESUME_CLASS_NAME) {
return state.set("refresh", false); 
}
// 4️⃣-㉒:编写替换“数据”的逻辑;
if(action.type === CHANGE_LIST) {
return state.set("list", action.data)
}
return state;
}

返回页面控值台查看(list 的数据项已经被替换了):
(07)Header 组件开发——⑦ AJAX 获取推荐数据 | React.js 项目实战:PC 端“简书”开发

OK,既然“数据”项 list 已成功被替换,接下来就好办了,我们只需要将 PanelLabels 样式组件里的内容,用 list 替换掉就可以了。

5️⃣打开 header 目录下的 index.js 文件:

import React, {Component} from "react";
import {
HeaderWrapper,
Logo,
Navbar,
ItemList,
LinkList,
SearchArea,
SearchInput,
SearchPanel,
PanelTitle,
PanelChange,
PanelLabels,
LabelLink,
Extra,
ExtraLink
} from "./style";
import { connect } from "react-redux";
import {actionCreators} from "./store";
class Header extends Component {
render() {
return (
<HeaderWrapper>
<Logo>
<img src="https://qdywxs.github.io/jianshu-images/logo.png" alt="logo" />
</Logo>
<Navbar className="clearfix">
<ItemList className="active">
<LinkList href="/">
首页
</LinkList>
</ItemList>
<ItemList>
<LinkList href="/">
下载APP
</LinkList>           
</ItemList>
</Navbar>
<SearchArea>
<SearchInput
onFocus={this.props.handleInputFocus}
/>
<span className="iconfont icon-search">&#xe63e;</span>
<SearchPanel>
<PanelTitle>
热门搜索
<PanelChange
onMouseDown={this.props.handleMouseDown}
onMouseUp={this.props.handleMouseUp}
> 
<span className={this.props.refresh ? "iconfont refresh" : "iconfont"}>&#xe65f;</span>
换一批
</PanelChange>
</PanelTitle>
<PanelLabels className="clearfix">
{/*
5️⃣-②:将这些写死的“数据”删除掉~
<LabelLink href="/">
区块链
</LabelLink>
<LabelLink href="/">
故事
</LabelLink>
<LabelLink href="/">
小程序
</LabelLink>
<LabelLink href="/">
前端一万小时
</LabelLink>
*/}
{/*
5️⃣-③:替换为“数据项 list”中的内容。❗️注意:虽然 list 
是“immutable 对象”,但 immutable 依然给我们提供了
一样功能的 map 方法;
*/}
{
this.props.list.map((item) => {
return <LabelLink key={item} href="/">{item}</LabelLink>
})
}
</PanelLabels>
</SearchPanel>
</SearchArea>
<Extra>
<span className="iconfont icon-textsize" >&#xe739;</span>
<ExtraLink className="login" href="/">
登录
</ExtraLink>
<ExtraLink className="register" href="/">
注册
</ExtraLink> 
<ExtraLink className="writing" href="/">
<span className="iconfont icon-pen">&#xe600;</span>
写文章
</ExtraLink>     
</Extra>
</HeaderWrapper>
)
}
}
const mapStateToProps = (state) => { 
return { 
refresh: state.getIn(["header", "refresh"]),
// ❗️5️⃣-①:从 header 下取得 list 数据;
list: state.getIn(["header", "list"])
}
}
const mapDispatchToProps = (dispatch) => {  
return {
handleMouseDown() { 
const action = actionCreators.changeClassNameAction(); 
dispatch(action)
},
handleMouseUp() {
const action = actionCreators.resumeClassNameAction();
dispatch(action)
},
handleInputFocus() {
const action = actionCreators.initLabelAction();
dispatch(action)
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Header); 

返回页面查看效果(“数据”正常显示,只是视频的最后几秒的 bug 依然没解决):
(07)Header 组件开发——⑦ AJAX 获取推荐数据 | React.js 项目实战:PC 端“简书”开发

5 避免无意义的请求发送

既然需求都实现了,我们现在就可以来解决一下“重复发送请求”的 bug。

6️⃣打开 header 目录下的 index.js 文件:

import React, {Component} from "react";
import {
HeaderWrapper,
Logo,
Navbar,
ItemList,
LinkList,
SearchArea,
SearchInput,
SearchPanel,
PanelTitle,
PanelChange,
PanelLabels,
LabelLink,
Extra,
ExtraLink
} from "./style";
import { connect } from "react-redux";
import {actionCreators} from "./store";
class Header extends Component {
render() {
return (
<HeaderWrapper>
<Logo>
<img src="https://qdywxs.github.io/jianshu-images/logo.png" alt="logo" />
</Logo>
<Navbar className="clearfix">
<ItemList className="active">
<LinkList href="/">
首页
</LinkList>
</ItemList>
<ItemList>
<LinkList href="/">
下载APP
</LinkList>           
</ItemList>
</Navbar>
<SearchArea>
<SearchInput
onFocus={() => this.props.handleInputFocus(this.props.list)}
/> {/*
❗️❗️❗️6️⃣-①:在给元素绑定 onfocus 事件时,我们可以同时给事件方法
传递一个 this.props.list 参数;
onFocus={this.props.handleInputFocus}
*/}
<span className="iconfont icon-search">&#xe63e;</span>
<SearchPanel>
<PanelTitle>
热门搜索
<PanelChange
onMouseDown={this.props.handleMouseDown}
onMouseUp={this.props.handleMouseUp}
> 
<span className={this.props.refresh ? "iconfont refresh" : "iconfont"}>&#xe65f;</span>
换一批
</PanelChange>
</PanelTitle>
<PanelLabels className="clearfix">
{
this.props.list.map((item) => {
return <LabelLink key={item} href="/">{item}</LabelLink>
})
}
</PanelLabels>
</SearchPanel>
</SearchArea>
<Extra>
<span className="iconfont icon-textsize" >&#xe739;</span>
<ExtraLink className="login" href="/">
登录
</ExtraLink>
<ExtraLink className="register" href="/">
注册
</ExtraLink> 
<ExtraLink className="writing" href="/">
<span className="iconfont icon-pen">&#xe600;</span>
写文章
</ExtraLink>     
</Extra>
</HeaderWrapper>
)
}
}
const mapStateToProps = (state) => { 
return { 
refresh: state.getIn(["header", "refresh"]),
list: state.getIn(["header", "list"])
}
}
const mapDispatchToProps = (dispatch) => {  
return {
handleMouseDown() { 
const action = actionCreators.changeClassNameAction(); 
dispatch(action)
},
handleMouseUp() {
const action = actionCreators.resumeClassNameAction();
dispatch(action)
},
handleInputFocus(list) { // 6️⃣-②:注意在这里接收 list;
// ❗️6️⃣-③:我们可以打印一下这个 list 都有些什么东西;
console.log(list);
const action = actionCreators.initLabelAction();
dispatch(action)
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Header); 

返回控制台查看(除了第一次请求时,list 的 size 为 0,其他都是 50):
(07)Header 组件开发——⑦ AJAX 获取推荐数据 | React.js 项目实战:PC 端“简书”开发

🚀利用这一特点,我们可以用 size 作一个判断(仅当 size === 0 时,我们才发送 AJAX 请求。即,没“数据”的时候才请求“数据”,有“数据”后就不要再请求了)。返回 header 目录下的 index.js 文件:

import React, {Component} from "react";
import {
HeaderWrapper,
Logo,
Navbar,
ItemList,
LinkList,
SearchArea,
SearchInput,
SearchPanel,
PanelTitle,
PanelChange,
PanelLabels,
LabelLink,
Extra,
ExtraLink
} from "./style";
import { connect } from "react-redux";
import {actionCreators} from "./store";
class Header extends Component {
render() {
return (
<HeaderWrapper>
<Logo>
<img src="https://qdywxs.github.io/jianshu-images/logo.png" alt="logo" />
</Logo>
<Navbar className="clearfix">
<ItemList className="active">
<LinkList href="/">
首页
</LinkList>
</ItemList>
<ItemList>
<LinkList href="/">
下载APP
</LinkList>           
</ItemList>
</Navbar>
<SearchArea>
<SearchInput
onFocus={() => this.props.handleInputFocus(this.props.list)}
/>
<span className="iconfont icon-search">&#xe63e;</span>
<SearchPanel>
<PanelTitle>
热门搜索
<PanelChange
onMouseDown={this.props.handleMouseDown}
onMouseUp={this.props.handleMouseUp}
> 
<span className={this.props.refresh ? "iconfont refresh" : "iconfont"}>&#xe65f;</span>
换一批
</PanelChange>
</PanelTitle>
<PanelLabels className="clearfix">
{
this.props.list.map((item) => {
return <LabelLink key={item} href="/">{item}</LabelLink>
})
}
</PanelLabels>
</SearchPanel>
</SearchArea>
<Extra>
<span className="iconfont icon-textsize" >&#xe739;</span>
<ExtraLink className="login" href="/">
登录
</ExtraLink>
<ExtraLink className="register" href="/">
注册
</ExtraLink> 
<ExtraLink className="writing" href="/">
<span className="iconfont icon-pen">&#xe600;</span>
写文章
</ExtraLink>     
</Extra>
</HeaderWrapper>
)
}
}
const mapStateToProps = (state) => { 
return { 
refresh: state.getIn(["header", "refresh"]),
list: state.getIn(["header", "list"])
}
}
const mapDispatchToProps = (dispatch) => {  
return {
handleMouseDown() { 
const action = actionCreators.changeClassNameAction(); 
dispatch(action)
},
handleMouseUp() {
const action = actionCreators.resumeClassNameAction();
dispatch(action)
},
handleInputFocus(list) {  
if(list.size === 0) { // ❗️6️⃣-④:仅当 size === 0 时,我们才发送 AJAX 请求!
const action = actionCreators.initLabelAction();
dispatch(action)
}
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Header); 

返回页面查看效果(一切正常显示,且 AJAX 请求只发送了一次):
(07)Header 组件开发——⑦ AJAX 获取推荐数据 | React.js 项目实战:PC 端“简书”开发

祝好,qdywxs ♥ you!

原文链接:https://juejin.cn/post/7340215899747156007 作者:itsOli

(0)
上一篇 2024年2月28日 下午5:09
下一篇 2024年2月29日 上午10:00

相关推荐

发表回复

登录后才能评论