# react 请求 github 用户信息案例

103 阅读6分钟

react 请求 github 用户信息案例

前边我们知道了 Axios 请求和代理,那么现在我们实现一个小的案例,就是通过 http 请求去查询 github 的用户信息。

查询 github 用户信息案例

这个案例很简单,就是编写一个页面,有一个输入框,输入关键字,然后通过 http 请求去 github 上去查询相关用户信息。

在这里插入图片描述

拆分组件

我们就暂时使用两个组件就可以了,上部分的 Search 用于输入框 输入并查询。下部分的 List 用于展示用户信息。

在这里插入图片描述 因为我们最终还要实现 Axios 请求,所以说我们配置代理的操作也要实现吧?

于是,我们最终主要的文件结构就是这个样子滴!

在这里插入图片描述

好了,下面我们就可以根据具体组件实现效果开发静态页面。

记住了哈,先不急着开发功能,先写静态页面,搭好架子在开发功能。

编写静态页面

Search 组件

import React, { Component } from 'react'

export default class Search extends Component {
  render() {
    return (
      <section className="jumbotron">
        <h3 className="jumbotron-heading">Search Github Users</h3>
        <div>
          <input type="text" placeholder="enter the name you search" />&nbsp;<button>Search</button>
        </div>
      </section>
    )
  }
}

List 组件

组件代码:

import React, { Component } from 'react';
import './index.css'

export default class List extends Component {
  render() {
    return (
      <div className="row">
        <div className="card">
          <a rel='noreferrer' href="https://github.com/reactjs" target="_blank">
            <img alt='我是𝒆𝒅.' src="https://avatars.githubusercontent.com/u/6412038?v=3" style={{ width: '100px' }} />
          </a>
          <p className="card-text">reactjs</p>
        </div>
        <div className="card">
          <a rel='noreferrer' href="https://github.com/reactjs" target="_blank">
            <img alt='我是𝒆𝒅.' src="https://avatars.githubusercontent.com/u/6412038?v=3" style={{ width: '100px' }} />
          </a>
          <p className="card-text">reactjs</p>
        </div>
        <div className="card">
          <a rel='noreferrer' href="https://github.com/reactjs" target="_blank">
            <img alt='我是𝒆𝒅.' src="https://avatars.githubusercontent.com/u/6412038?v=3" style={{ width: '100px' }} />
          </a>
          <p className="card-text">reactjs</p>
        </div>
        <div className="card">
          <a rel='noreferrer' href="https://github.com/reactjs" target="_blank">
            <img alt='我是𝒆𝒅.' src="https://avatars.githubusercontent.com/u/6412038?v=3" style={{ width: '100px' }} />
          </a>
          <p className="card-text">reactjs</p>
        </div>
        <div className="card">
          <a rel='noreferrer' href="https://github.com/reactjs" target="_blank">
            <img alt='我是𝒆𝒅.' src="https://avatars.githubusercontent.com/u/6412038?v=3" style={{ width: '100px' }} />
          </a>
          <p className="card-text">reactjs</p>
        </div>
      </div>
    );
  }
}

样式组件:

.album {
  min-height: 50rem;
  /* Can be removed; just added for demo purposes */
  padding-top: 3rem;
  padding-bottom: 3rem;
  background-color: #f7f7f7;
}

.card {
  float: left;
  width: 33.333%;
  padding: .75rem;
  margin-bottom: 2rem;
  border: 1px solid #efefef;
  text-align: center;
}

.card>img {
  margin-bottom: .75rem;
  border-radius: 100px;
}

.card-text {
  font-size: 85%;
}

App 组件

// 创建外壳组件
import React, { Component } from "react";
import List from "./components/List";
import Search from "./components/Search";
import axios from 'axios'

// 创建并暴露APP组件
export default class App extends Component {

  render() {
    return (
      <div className="container">
        <Search />
        <List />
      </div>
    )
  }
}

注意这里我们需要引入一下 bootstrap.css

在 public 文件夹下创建 css 文件夹,将 bootstrap.css 文件放进去,bootstrap.css 文件在 gitee。

最后在 index.html 文件引入一下。

<link rel="stylesheet" href="./css/bootstrap.css">

编写完成之后,保存刷新。

在这里插入图片描述 静态页面开发完,我们就可以是先具体功能了。

【本部分相关代码资料】: 我是𝒆𝒅. 的 gitee

请求数据

接下来项目中最重要的就是请求 github 数据了吧?

OK,请求的地址就是这个地址 https://api.github.com/search/users?q=xxxxxx

首先这是一个 get 请求,参数 q 就是我们输入框需要输入的关键字,既然是 get 请求我们就可以借助浏览器地址栏先看一下效果吧?

试一下:

在这里插入图片描述 OK,接口没有问题,数据成功请求回来了,既然在 react 调用需要解决跨域问题吧?

我们编写一个 setupProxy.js 文件。

const { createProxyMiddleware: proxy } = require('http-proxy-middleware')

module.exports = function (app) {
  app.use(
    proxy('/api', {   // 遇到 /api 前缀的请求,就会触发该代理配置
      target: 'https://api.github.com',  // 请求转发给谁
      changeOrigin: true,   // 控制服务器收到的响应头中的 Host 字段值 (建议都写)
      pathRewrite: { '^/api': '' }  // 重写请求路径(必须写)
    })
  )
}

好的,我们是在哪里请求接口啊?没错,是在 Search 组件吧?那我们写一个请求,我们先不渲染,单纯看一下能不能获取数据。

要实现的功能点

  • 首先引入axios。
  • 获取用户输入的关键字。
  • 点击按钮触发事件发出请求获取数据。
import React, { Component } from 'react'
import axios from 'axios'

export default class Search extends Component {

  // 搜索按钮点击回调
  search = () => {
    // 获取用户输入数据
    // let { value } = this.keywordElement  // 常规解构赋值
    let { keywordElement: { value: keyword } } = this  // 连续解构并重命名
    // 发起网络请求
    axios.get(`http://localhost:3000/api/search/users?q=${keyword}`).then(
      response => {
        console.log("请求成功了--->", response.data)
      },
      error => {
        console.log("请求失败了--->", error)
      });
  }

  render() {
    return (
      <section className="jumbotron">
        <h3 className="jumbotron-heading">搜索 GitHub 用户</h3>
        <div>
          <input ref={c => this.keywordElement = c} type="text" placeholder="输入关键词搜索" />&nbsp;
          <button onClick={this.search}>搜索</button>
        </div>
      </section>
    )
  }
}

其中啊,这句话使用了连续解构并重命名:

let { keywordElement: { value: keyword } } = this  // 连续解构并重命名

这样的话,那个 keyword 就是用户输入的内容对吧?解构方面的知识点,不会的话去看 ES6 语法中的解构部分。

ES6小知识点:解构赋值+重命名 let obj = {a:{b:1}} const {a} = obj; //传统解构赋值 const {a:{b}} = obj; //连续解构赋值 const {a:{b:value}} = obj; //连续解构赋值+重命名

好的, 我们保存代码,输入关键字,点击按钮,看一下打印的数据有没有。

在这里插入图片描述

【本部分相关代码资料】: 我是𝒆𝒅. 的 gitee

渲染数据

好的,我们现在已经获取了从 github 查询到的用户信息了,接下来就是渲染,我们先使用父子组件传值的方式实现一下,这里有点小知识需要注意一下。

关于父子之间通信: 1.【父组件】给【子组件】传递数据:通过props传递 2.【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数

好,我们查询是在 Search 组件进行的,查询到的数据需要在 List 组件渲染啊

所以说,我们需要把维护一个列表用于存放请求回来的用户列表信息,请问这个列表信息应该维护在哪里最好? 对了,就是 APP 组件里面,因为用户列表信息 List 组件用,我们需要把 列表传给 List 组件,但是 Search 组件需要给 列表赋值,所以说放在父组件里面最好,Search 组件给父组件的列表赋值完毕,List 组件直接就可以通过 props 获取到父组件的 列表数据进行展示了对吧。

App 组件

需要创建一个 状态值 users 存放 Search 组件返回来的用户列表信息啊,而且还得把这个列表信息传给他的子组件 List 去渲染展示呀。

// 创建外壳组件
import React, { Component } from "react";
import List from "./components/List";
import Search from "./components/Search";

// 创建并暴露APP组件
export default class App extends Component {

  // 初始化状态
  state = {
    users: [], // 初始化用户列表
  }

  // 保存用户信息列表
  saveUsers = (users) => {
    this.setState({ users })
  }

  render() {
    const { users } = this.state
    return (
      <div className="container">
        <Search saveUsers={this.saveUsers} />
        <List users={users} />
      </div>
    )
  }
}

Search 组件

这个组件需要查询出数据,并且把数据返给父组件给 状态的 users 列表重新赋值吧。

import React, { Component } from 'react'
import axios from 'axios'

export default class Search extends Component {

  // 搜索按钮点击回调
  search = () => {
    // 获取用户输入数据
    // let { value } = this.keywordElement  // 常规解构赋值
    let { keywordElement: { value: keyword } } = this  // 连续解构并重命名
    // 发起网络请求
    axios.get(`/api/search/users?q=${keyword}`).then(
      response => {
        this.props.saveUsers(response.data.items)
        console.log("请求成功了--->", response.data)
      },
      error => {
        console.log("请求失败了--->", error)
      });
  }

  render() {
    return (
      <section className="jumbotron">
        <h3 className="jumbotron-heading">搜索 GitHub 用户</h3>
        <div>
          <input ref={c => this.keywordElement = c} type="text" placeholder="输入关键词搜索" />&nbsp;
          <button onClick={this.search}>搜索</button>
        </div>
      </section>
    )
  }
}

List 组件

List 组件接收到父组件传进来的 users 列表然后就可以直接渲染展示了对吧。

import React, { Component } from 'react';
import './index.css'

export default class List extends Component {
  render() {
    return (
      <div className="row">

        {this.props.users.map(userObj => {
          return (
            <div className="card" key={userObj.id}>
              <a rel='noreferrer' href={userObj.html_url} target="_blank">
                <img alt='我是𝒆𝒅.' src={userObj.avatar_url} style={{ width: '100px' }} />
              </a>
              <p className="card-text">{userObj.login}</p>
            </div>
          )
        })}
      </div>
    );
  }
}

OK,到此为止,查询渲染就可以的。

在这里插入图片描述

【本部分相关代码资料】: 我是𝒆𝒅. 的 gitee

完善功能

到上面一部分,我们的功能是实现了的,但是效果不是很好。

暂存的瑕疵:

  • 第一次进入系统,没有请求的时候,users 是空的,然后需要展示一段话“输入关键字,点击搜索按钮进行搜索”。
  • 请求数据需要时间的,在请求的过程中需要显示 “正在查询,请稍等...”。
  • 如果请求出现错误,需要显示错误信息。
  • 以上问题都没有发生才说明数据请求成功,才可以正常展示数据吧。

所以说我们需要给每一个状况,都要设置一个变量进行页面渲染的控制吧?

变量设在哪里?对的,还是 App 组件吧?接下来我们实现一下。

App 组件

我们在 state 状态里面继续添加三个状态值,用来管理每个场景的状态。

isFirst: true, // 是否是第一次打开 isLoading: false, // 是否正在加载 err: '' // 错误信息

第一次打开页面,isFirst 一定是 true吧。 刚进入界面还没有发起请求,所以说 isLoading 也一定是 false。错误信息 err 应该也是没有的吧?所以初始状态这么设置没有异议吧。

然后我们之前有一个方法来获取 Search 组件传递回来的 users 信息,但是现在是在 Search 里面发起的请求操作,所以说除了 users 数据,isFirst、isLoading、err 信息也是 Search 组件来改变吧?所以我们直接写一个大的存储状态的方法 saveAppState 让 Search 返回给父组件保存就可以了。

// 创建外壳组件
import React, { Component } from "react";
import List from "./components/List";
import Search from "./components/Search";

// 创建并暴露APP组件
export default class App extends Component {

  // 初始化状态
  state = {
    users: [], // 初始化用户列表
    isFirst: true, // 是否是第一次打开
    isLoading: false,  // 是否正在加载
    err: ''  // 错误信息
  }

  // 更新APP的状态
  saveAppState = (stateObj) => {
    this.setState(stateObj)
  }

  render() {
    return (
      <div className="container">
        <Search saveAppState={this.saveAppState} />
        <List {...this.state} />
      </div>
    )
  }
}

Search 组件

除了最开始请求成功 github 返回的 users 数据外,Search 组件还需要处理状态了,他返回给父组件,父组件修改了 state 值,List 组件就可以接收到,然后根据不同的状态,可以渲染不同的文字提示吧。

import React, { Component } from 'react'
import axios from 'axios'

export default class Search extends Component {

  // 搜索按钮点击回调
  search = () => {
    // 获取用户输入数据
    // let { value } = this.keywordElement  // 常规解构赋值
    let { keywordElement: { value: keyword } } = this  // 连续解构并重命名
    // 发起请求前通知APP修改状态
    this.props.saveAppState({
      isFirst: false,
      isLoading: true
    })
    // 发起网络请求
    axios.get(`/api/search/users?q=${keyword}`).then(
      response => {
        this.props.saveAppState({
          isLoading: false,
          users: response.data.items
        })
      },
      error => {
        this.props.saveAppState({
          isLoading: false,
          err: '请求出错! ' + error
        })
      });
  }

  render() {
    return (
      <section className="jumbotron">
        <h3 className="jumbotron-heading">搜索 GitHub 用户</h3>
        <div>
          <input ref={c => this.keywordElement = c} type="text" placeholder="输入关键词搜索" />&nbsp;
          <button onClick={this.search}>搜索</button>
        </div>
      </section>
    )
  }
}

List 组件

父组件的 state 改变了,他能获取到现在是在什么状态,然后根据那三个状态渲染不同的文字信息就可以了吧。

import React, { Component } from 'react';
import './index.css'

export default class List extends Component {
  render() {
    const { users, isFirst, isLoading, err } = this.props
    return (
      <div className="row">

        {
          isFirst ? <h2>输入关键字,点击搜索按钮进行搜索</h2> :
            isLoading ? <h2>正在查询,请稍等...</h2> :
              err ? <h2 style={{ color: 'red' }}>{err}</h2> :
                users.map(userObj => {
                  return (
                    <div className="card" key={userObj.id}>
                      <a rel='noreferrer' href={userObj.html_url} target="_blank">
                        <img alt='我是𝒆𝒅.' src={userObj.avatar_url} style={{ width: '100px' }} />
                      </a>
                      <p className="card-text">{userObj.login}</p>
                    </div>
                  )
                })}


      </div>
    );
  }
}

在渲染数据的时候需要根据状态选择不同的文字信息。但是,{} 中只能写 js 表达式,不能写 if 语句吧?所以说我们使用了 三元表达式

OK,上面操作完成,效果就全部实现了。

在这里插入图片描述 【本部分相关代码资料】: 我是𝒆𝒅. 的 gitee

PubSub插件使用

上一篇博客我们了解了通过 PubSub 插件实现兄弟组件传值,这样的话就不需要 父组件 在做周转了吧?接下来我们实现一下,把之前的代码优化掉。

App 组件

由于直接兄弟组件传值,App 组件就可以不去操作 state了吧?所有的展示其实都是 List 组件根据状态值不同在操作。

// 创建外壳组件
import React, { Component } from "react";
import List from "./components/List";
import Search from "./components/Search";

// 创建并暴露APP组件
export default class App extends Component {


  render() {
    return (
      <div className="container">
        <Search />
        <List />
      </div>
    )
  }
}

回归最原始的自己,代码的尽头是开始。

Search 组件

Search 组件获取数据或者是切换状态之后,状态值不需要在传递给父组件,而是直接通过 PubSub 插件传值给 List 组件就可以了吧?

使用 PubSub 插件,首先引入,在发布就可以了。

import React, { Component } from 'react'
import PubSub from 'pubsub-js'
import axios from 'axios'

export default class Search extends Component {

  // 搜索按钮点击回调
  search = () => {
    // 获取用户输入数据
    // let { value } = this.keywordElement  // 常规解构赋值
    let { keywordElement: { value: keyword } } = this  // 连续解构并重命名
    // 发起请求前通知List修改状态
    // this.props.saveAppState({ isFirst: false, isLoading: true })
    PubSub.publish('ed', { isFirst: false, isLoading: true })
    // 发起网络请求
    axios.get(`/api/search/users?q=${keyword}`).then(
      response => {
        // this.props.saveAppState({ isLoading: false, users: response.data.items })
        PubSub.publish('ed', { isLoading: false, users: response.data.items })
      },
      error => {
        // this.props.saveAppState({ isLoading: false, err: '请求出错! ' + error })
        PubSub.publish('ed', { isLoading: false, err: '请求出错! ' + error })
      });
  }

  render() {
    return (
      <section className="jumbotron">
        <h3 className="jumbotron-heading">搜索 GitHub 用户</h3>
        <div>
          <input ref={c => this.keywordElement = c} type="text" placeholder="输入关键词搜索" />&nbsp;
          <button onClick={this.search}>搜索</button>
        </div>
      </section>
    )
  }
}

List 组件

状态全部自己维护,直接把父组件当时维护的 state 复制过来就可以吧,本来也就只要他用。

List 组件需要订阅消息,才能获得 Search 组件返回的数据吧?其他都和父组件传值是一样的。

OK,什么时候开启订阅?什么时候关闭订阅?一定要关闭哈,不然会出大问题。

生命周期啊,之前学习过生命周期,所以使用生命周期在这里。

import React, { Component } from 'react';
import PubSub from 'pubsub-js'
import './index.css'

export default class List extends Component {

  // 初始化状态
  state = {
    users: [], // 初始化用户列表
    isFirst: true, // 是否是第一次打开
    isLoading: false,  // 是否正在加载
    err: ''  // 错误信息
  }

  componentDidMount() {
    this.token = PubSub.subscribe('ed', (_, data) => {
      this.setState(data)
    })
  }

  componentWillUnmount(){
    PubSub.unsubscribe(this.token)
  }

  render() {
    const { users, isFirst, isLoading, err } = this.state
    return (
      <div className="row">

        {
          isFirst ? <h2>输入关键字,点击搜索按钮进行搜索</h2> :
            isLoading ? <h2>正在查询,请稍等...</h2> :
              err ? <h2 style={{ color: 'red' }}>{err}</h2> :
                users.map(userObj => {
                  return (
                    <div className="card" key={userObj.id}>
                      <a rel='noreferrer' href={userObj.html_url} target="_blank">
                        <img alt='我是𝒆𝒅.' src={userObj.avatar_url} style={{ width: '100px' }} />
                      </a>
                      <p className="card-text">{userObj.login}</p>
                    </div>
                  )
                })}


      </div>
    );
  }
}

好的,上面代码修改完成之后,效果完全一样。

在这里插入图片描述

【本部分相关代码资料】: 我是𝒆𝒅. 的 gitee

好了,今天就到这里,再见!

在这里插入图片描述