「框架篇」Axios遇到React如何优雅的实现实时搜索
本文所使用的React
为最新版16.83,其中用到了高阶组件react.memo()
,处理异步请求的 async/await,基于Promise 的 HTTP 库axios
用于数据请求,以及用来取消请求的 axios APIaxios.CancelToken.source()
,用心看下去你会有所收获。
在文中,我们将使用 React 结合 Axios 在应用中构建实时搜索功能。会对不必要的请求进行处理优化,并且还对HTTP请求数据进行缓存等。
初始化应用程序
本文中,我们使用 react 官方提供的 Create React App 来初始化应用程序,需要 node >= 6.0,npm >= 5.2 。然后运行以下命名:
npx create-react-app axios-react
cd axios-react
npm start or yarn start
应用程序初始化后,安装 axios:
npm install axios or yarn add axios
接下来,将下面的代码复制到 App.js
组件中,我们先不用管 Movies
组件里是什么。
import React, { Component } from 'react';
import axios from 'axios';
import Movies from './Movies;
class App extends Component {
//state 定义我们所需要的数据
state = {
value: '',
movies: null,
loading: false
}
search = async val => {
this.setState({ loading: true });
const res = await axios(
`https://api.themoviedb.org/3/search/moviequery=${val}&api_key=dbc0a6d62448554c27b6167ef7dabb1b`
);
const movies = await res.data.results;
this.setState({ movies, loading: false });
};
onChangeHandler = async e => {
this.search(e.target.value);
this.setState({
value: e.target.value
})
}
get renderMovies() {
let movies = 没有搜索结果
if(this.state.loading) {
movies = LOADING ...
}
if(this.state.movies) {
movies = ;
}
return movies;
}
render() {
return (
this.onChangeHandler(e)}
placeholder="试着输入一些内容"
/>
{this.renderMovies}
);
}
}
export default App;
输入框内容改变
从代码中我们看到有一个受控 input
元素,当输入内容时,调用onChangeHandler
方法。onChangeHandler
方法做了两件事情:一是调用 search 方法并将输入的内容传参给 search
;二是修改 state 中的 value属性。
onChangeHandler = async e => {
//调用search 方法
this.search(e.target.value);
//更改state
this.setState({
value: e.target.value
})
}
搜索
在 search
方法中,我们使用 GET 请求,从API获取我们想要的数据。一旦请求发送成功时,我们就会更新组件 state
的 loading 属性为 true,表示正在等待结果。当我们得到返回的结果时,将更新 state movies 属性,并关闭请求状态。我们使用了 async/await
来处理异步请求,这里先不对这个做过多的介绍。
search = async val => {
this.setState({ loading: true });
const res = await axios(
`https://api.themoviedb.org/3/search/moviequery=${val}&api_key=dbc0a6d62448554c27b6167ef7dabb1b`
);
const movies = await res.data.results;
this.setState({ movies, loading: false });
};
渲染数据
在这里我们使用名为 renderMovies
的 get 方法通过 props
将获取到的数据传递给 Movies
组件。
get renderMovies() {
let movies = 没有搜索结果
if(this.state.loading) {
movies = LOADING ...
}
if(this.state.movies) {
movies = ;
}
return movies;
}
Movies组件
我们在src 目录下创建 Movies.js
文件,并复制下边代码到此文件中。
import React from 'react';
const Movie = props => {
let movie = 暂无数据
;
if(props.list.length) {
movie = props.list.map((item, index) => {
const { title, poster_path, vote_average } = item;
const src = poster_path && `url(http://image.tmdb.org/t/p/w185${poster_path})`;
return (
{vote_average}
{title}
)
})
}
return movie;
}
//16.6版本中更新了一些包装函数
//其中 React.memo() 是一个高阶函数
//它与 React.PureComponent类似
//但是一个纯函数组件而非一个类
const Movies = React.memo(Movie);
export default Movies;
Movies 组件做的事情很简单,就是将从 props 里接收过来的数据进行解析,并渲染到页面上,这里用到了包装函数 (wrapped functions) Reacta.memo()
用来优化应用性能,这里不对这个函数进行过多的介绍。
就是这么简单?
防止不必要的请求
我们打开浏览中的开发者工具 network 选项,您可能会注意到我们每次更新输入时都会发送请求,并且会有大量的重复请求,这可能导致请求过载,尤其是当我们收到大量响应时。
在解决这个问题之前,我们先在 src 目录下创建 utils.js
文件,将以下代码复制进去。
import axios from 'axios';
const axiosRequester = () => {
let cancel;
return async url => {
if(cancel) {
//如果token存在,就取消请求
cancel.cancel();
}
//创建一个新的cancelToken
cancel = axios.CancelToken.source();
try {
const res = await axios(url, {
cancelToken: cancel.token
})
const result = res.data.results;
return result;
} catch(error) {
if(axios.isCancel(error)) {
console.log('Request canceled', error.message);
} else {
console.log(error.message);
}
}
}
}
export const _search = axiosRequester();
修改 App
组件中的代码:
...
import { _search } from './utils';
class App extends Component {
...
search = async val => {
this.setState({ loading: true
}
const url = `https://api.themoviedb.org/3/search/movie?query=${val}&api_key=dbc0a6d62448554c27b6167ef7dabb1b`
const res = await _search(url);
const movies = res;
this.setState({ movies, loading: false });
}
...
我们都做了什么
Axios
提供了所谓的取消令牌(cancel token)功能,允许我们取消请求。
在 axiosRequester
我们创建一个名为的 cancel 变量。然后发送请求,如果 cancel 变量存在,我们调用其 cancel 方法取消先前的请求。然后我们分配 一个 新的 token
给 CancelToken
。之后,我们使用给定的查询发出请求并返回结果。
我们使用 try/catch
对问题进行捕获,我们可以检查并处理请求是否被取消。
我们现在看看开发者工具中 network 是什么样子的:
我们看到请求都被我们取消掉了,因为这些请求之前已经请求过了。
缓存HTTP请求和数据
如果我们在多次输入中键入相同的文本,我们每次都会发出一次新的请求,这显然不是我们想要的结果。解决这个问题,我们将 utils.js
稍微改变下:
import axios from 'axios';
//用来存储已经请求过的数据
const resources = {};
const axiosRequester = () => {
let cancel;
return async url => {
if(cancel) {
//如果token存在,就取消请求
cancel.cancel();
}
//创建一个新的cancelToken
cancel = axios.CancelToken.source();
try {
//如果请求的数据已经存在,缓存之前请求回来的数据
if(resources[url]) {
return resources[url];
}
const res = await axios.post(url, {
cancelToken: cancel.token
})
const result = res.data.results;
//将url作为key, 记录请求回来的数据
resources[url] = result;
return result;
} catch(error) {
if(axios.isCancel(error)) {
console.log('Request canceled', error.message);
} else {
console.log(error.message);
}
}
}
}
export const _search = axiosRequester();
这里我们创建了一个 resources
对象用来缓存我们请求的结果。当正在执行新请求时,我们首先检查我们的 resources
对象是否具有此次查询的结果。如果存在,我们只返回该结果。如果不存在,我们会发出新请求并将对应的结果存储到 resources
中。
让我们用几句话总结一下,当我们在input框中输入内容时:
如果有过的话,我们取消之前的请求。如果我们已经输入了之前的内容,我们只需返回之前的数据,不会发出新请求。
如果是新的请求,我们会将获得的新的数据缓存起来。
如果想要了解更多,请搜索微信公众号:webinfoq
。