Axios遇到React如何优雅的实现实时搜索

「框架篇」Axios遇到React如何优雅的实现实时搜索

本文所使用的 React 为最新版16.83,其中用到了高阶组件 react.memo(),处理异步请求的 async/await,基于Promise 的 HTTP 库 axios 用于数据请求,以及用来取消请求的 axios API axios.CancelToken.source(),用心看下去你会有所收获。

在文中,咱们将使用 React 结合 Axios 在应用中构建实时搜索功能。会对没必要要的请求进行处理优化,而且还对HTTP请求数据进行缓存等。java

初始化应用程序

本文中,咱们使用 react 官方提供的 Create React App 来初始化应用程序,须要 node >= 6.0,npm >= 5.2 。而后运行如下命名:node

npx create-react-app axios-react
cd axios-react
npm start or yarn start

应用程序初始化后,安装 axios:react

npm install axios or yarn add axios

接下来,将下面的代码复制到 App.js 组件中,咱们先不用管 Movies 组件里是什么。ios

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 = <h1>没有搜索结果</h1>
    if(this.state.loading) {
      movies = <h1>LOADING ...</h1>
    }
    if(this.state.movies) {
      movies = <Movies list={this.state.movies}></Movies>;
    }   
    return movies;
  }

  render() {
    return (    
      <div>
        <input
          className="Search"
          value={this.state.value}
          onChange={e => this.onChangeHandler(e)}
          placeholder="试着输入一些内容"
        />
        <div className="ContainerInner">
          {this.renderMovies}
        </div>
       </div>
     );
   }
 }
 export default App;

输入框内容改变

从代码中咱们看到有一个受控 input 元素,当输入内容时,调用onChangeHandler 方法。onChangeHandler 方法作了两件事情:一是调用 search 方法并将输入的内容传参给 search;二是修改 state 中的 value属性。web

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 来处理异步请求,这里先不对这个作过多的介绍。npm

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 组件。axios

get renderMovies() {
    let movies = <h1>没有搜索结果</h1>
    if(this.state.loading) {
      movies = <h1>LOADING ...</h1>
    }
    if(this.state.movies) {
      movies = <Movies list={this.state.movies}></Movies>;
    }
   return movies;
}

Movies组件

咱们在src 目录下建立 Movies.js 文件,并复制下边代码到此文件中。api

import React from 'react';
const Movie = props => {
  let movie = <h1>暂无数据</h1>;
  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 (
        <div className="Container"
             style={{ backgroundImage: src }}
             key={index}>
          <div className="VoteContainer">
            <span className="Vote">{vote_average}</span>
          </div>
         <div className="Bottom">
            <h3 className="Title">{title}</h3>
          </div>
        </div>
      )
    })
  }
  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 方法取消先前的请求。而后咱们分配 一个 新的 tokenCancelToken。以后,咱们使用给定的查询发出请求并返回结果。

咱们使用 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

相关文章
相关标签/搜索