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

相關文章
相關標籤/搜索