第4天:React ajax

  • 以下代码示例的参考代码只提供源码文件夹/src目录下的文件。
  • 静态资源文件夹因为都一样,所以就不提供了,可以点击这里下载public目录
  • *代码的使用:根据步骤3.1.2执行完之后,替换掉相关文件夹即可。

4.1. 理解

4.1.1. 前置说明

  1. React本身只关注于界面, 并不包含发送ajax请求的代码
  2. 前端应用需要通过ajax请求与后台进行交互(json数据)
  3. react应用中需要集成第三方ajax库(或自己封装)

4.1.2. 常用的ajax请求库

  1. jQuery: 比较重, 如果需要另外引入不建议使用
  2. axios: 轻量级, 建议使用
    1) 封装XmlHttpRequest对象的ajax
    2) promise风格
    3) 可以用在浏览器端和node服务器端

4.2. axios

4.2.1. 文档

https://github.com/axios/axios

4.2.2. 相关API

  1. GET请求

    axios.get('/user?ID=12345')
    .then(function (response) {
    console.log(response.data);
    })
    .catch(function (error) {
    console.log(error);
    });
    //
    axios.get('/user', {
    params: {
      ID: 12345
    }
    })
    .then(function (response) {
    console.log(response);
    })
    .catch(function (error) {
    console.log(error);
    });
  2. POST请求

    axios.post('/user', {
    firstName: 'Fred',
    lastName: 'Flintstone'
    })
    .then(function (response) {
    console.log(response);
    })
    .catch(function (error) {
    console.log(error);
    });

4.2.3. 配置代理

方法一

在package.json中追加如下配置

"proxy":"http://localhost:5000"

说明:

  1. 优点:配置简单,前端请求资源时可以不加任何前缀。
  2. 缺点:不能配置多个代理。
  3. 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
方法二
  1. 第一步:创建代理配置文件
    在src下创建配置文件:src/setupProxy.js

  2. 编写setupProxy.js配置具体代理规则:

    const proxy = require('http-proxy-middleware')
    //
    module.exports = function(app) {
    app.use(
    proxy('/api1', {  //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
      target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
      changeOrigin: true, //控制服务器接收到的请求头中host字段的值
      /*
        changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
        changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
        changeOrigin默认值为false,但我们一般将changeOrigin值设为true
      */
      pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
    }),
    proxy('/api2', { 
      target: 'http://localhost:5001',
      changeOrigin: true,
      pathRewrite: {'^/api2': ''}
    })
    )
    }

说明:

  1. 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
  2. 缺点:配置繁琐,前端请求资源时必须加前缀。

4.3. 案例—github用户搜索

4.3.1. 基础

请求地址: https://api.github.com/search/users?q=xxxxxx

代码

  • src/App.jsx

    import React, { Component } from 'react'
    import Search from './components/Search'
    import List from './components/List'
    //
    export default class App extends Component {
    
    state = { //初始化状态
        users:[], //users初始值为数组
        isFirst:true, //是否为第一次打开页面
        isLoading:false,//标识是否处于加载中
        err:'',//存储请求相关的错误信息
    }
    
    //更新App的state
    updateAppState = (stateObj)=>{
        this.setState(stateObj)
    }
    
    render() {
        return (
            <div className="container">
                <Search updateAppState={this.updateAppState}/>
                <List {...this.state}/>
            </div>
        )
    }
    }
  • src/index.js

    //引入react核心库
    import React from 'react'
    //引入ReactDOM
    import ReactDOM from 'react-dom'
    //引入App
    import App from './App'
    //
    ReactDOM.render(<App/>,document.getElementById('root'))
  • src/components/List/index.css

    .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%;
    }
  • src/components/List/index.jsx

    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>Loading......</h2> :
                            err ? <h2 style={{ color: 'red' }}>{err}</h2> :
                                users.map((userObj) => {
                                    return (
                                        <div key={userObj.id} className="card">
                                            <a rel="noreferrer" href={userObj.html_url} target="_blank">
                                                <img alt="head_portrait" src={userObj.avatar_url} style={{ width: '100px' }} />
                                            </a>
                                            <p className="card-text">{userObj.login}</p>
                                        </div>
                                    )
                                })
                }
            </div>
        )
    }
    }
  • src/components/Search/index.jsx

    import React, { Component } from 'react'
    import axios from 'axios'
    //
    export default class Search extends Component {
    
    search = ()=>{
        //获取用户的输入(连续解构赋值+重命名)
        const {keyWordElement:{value:keyWord}} = this
        //发送请求前通知App更新状态
        this.props.updateAppState({isFirst:false,isLoading:true})
        //发送网络请求
        axios.get(`/api1/search/users?q=${keyWord}`).then(
            response => {
                //请求成功后通知App更新状态
                this.props.updateAppState({isLoading:false,users:response.data.items})
            },
            error => {
                //请求失败后通知App更新状态
                this.props.updateAppState({isLoading:false,err:error.message})
            }
        )
    }
    
    render() {
        return (
            <section className="jumbotron">
                <h3 className="jumbotron-heading">搜索github用户</h3>
                <div>
                    <input ref={c => this.keyWordElement = c} type="text" placeholder="输入关键词点击搜索"/> 
                    <button onClick={this.search}>搜索</button>
                </div>
            </section>
        )
    }
    }

4.4. 消息订阅-发布机制

  1. 工具库: PubSubJS
  2. 下载: npm install pubsub-js –save
  3. 使用:
    1) import PubSub from ‘pubsub-js’ //引入
    2) PubSub.subscribe(‘delete’, function(data){ }); //订阅
    3) PubSub.publish(‘delete’, data) //发布消息

PubSubJS应用代码

  • src/App.jsx

    import React, { Component } from 'react'
    import Search from './components/Search'
    import List from './components/List'
    //
    export default class App extends Component {
    render() {
        return (
            <div className="container">
                <Search/>
                <List/>
            </div>
        )
    }
    }
  • src/index.js

    //引入react核心库
    import React from 'react'
    //引入ReactDOM
    import ReactDOM from 'react-dom'
    //引入App
    import App from './App'
    //
    ReactDOM.render(<App/>,document.getElementById('root'))
  • src/setupProxy.js

    const proxy = require('http-proxy-middleware')
    //
    module.exports = function(app){
    app.use(
        proxy('/api1',{ //遇见/api1前缀的请求,就会触发该代理配置
            target:'http://localhost:5000', //请求转发给谁
            changeOrigin:true,//控制服务器收到的请求头中Host的值
            pathRewrite:{'^/api1':''} //重写请求路径(必须)
        })
    )
    }
  • src/components/List/index.css

    .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%;
    }
  • src/components/List/index.jsx

    import React, { Component } from 'react'
    import PubSub from 'pubsub-js'
    import './index.css'
    //
    export default class List extends Component {
    
    state = { //初始化状态
        users:[], //users初始值为数组
        isFirst:true, //是否为第一次打开页面
        isLoading:false,//标识是否处于加载中
        err:'',//存储请求相关的错误信息
    }
    
    componentDidMount(){
        this.token = PubSub.subscribe('atguigu',(_,stateObj)=>{
            this.setState(stateObj)
        })
    }
    
    componentWillUnmount(){
        PubSub.unsubscribe(this.token)
    }
    
    render() {
        const {users,isFirst,isLoading,err} = this.state
        return (
            <div className="row">
                {
                    isFirst ? <h2>欢迎使用,输入关键字,随后点击搜索</h2> :
                    isLoading ? <h2>Loading......</h2> :
                    err ? <h2 style={{color:'red'}}>{err}</h2> :
                    users.map((userObj)=>{
                        return (
                            <div key={userObj.id} className="card">
                                <a rel="noreferrer" href={userObj.html_url} target="_blank">
                                    <img alt="head_portrait" src={userObj.avatar_url} style={{width:'100px'}}/>
                                </a>
                                <p className="card-text">{userObj.login}</p>
                            </div>
                        )
                    })
                }
            </div>
        )
    }
    }
  • src/components/Search/index.jsx

    import React, { Component } from 'react'
    import PubSub from 'pubsub-js'
    import axios from 'axios'
    //
    export default class Search extends Component {
    
    search = ()=>{
        //获取用户的输入(连续解构赋值+重命名)
        const {keyWordElement:{value:keyWord}} = this
        //发送请求前通知List更新状态
        PubSub.publish('atguigu',{isFirst:false,isLoading:true})
        //发送网络请求
        axios.get(`/api1/search/users?q=${keyWord}`).then(
            response => {
                //请求成功后通知List更新状态
                PubSub.publish('atguigu',{isLoading:false,users:response.data.items})
            },
            error => {
                //请求失败后通知App更新状态
                PubSub.publish('atguigu',{isLoading:false,err:error.message})
            }
        )
    }
    
    render() {
        return (
            <section className="jumbotron">
                <h3 className="jumbotron-heading">搜索github用户</h3>
                <div>
                    <input ref={c => this.keyWordElement = c} type="text" placeholder="输入关键词点击搜索"/> 
                    <button onClick={this.search}>搜索</button>
                </div>
            </section>
        )
    }
    }

4.5. 扩展:Fetch

4.5.1. 文档

  1. https://github.github.io/fetch/
  2. https://segmentfault.com/a/1190000003810652

4.5.2. 特点

  1. fetch: 原生函数,不再使用XmlHttpRequest对象提交ajax请求
  2. 老版本浏览器可能不支持

4.5.3. 相关API

  1. GET请求

    fetch(url).then(function(response) {
    return response.json()
    }).then(function(data) {
    console.log(data)
    }).catch(function(e) {
    console.log(e)
    });
  2. POST请求

    fetch(url, {
    method: "POST",
    body: JSON.stringify(data),
    }).then(function(data) {
    console.log(data)
    }).catch(function(e) {
    console.log(e)
    })

Fetch应用代码

  • src/App.jsx

    import React, { Component } from 'react'
    import Search from './components/Search'
    import List from './components/List'
    //
    export default class App extends Component {
    render() {
        return (
            <div className="container">
                <Search/>
                <List/>
            </div>
        )
    }
    }
  • src/index.js

    //引入react核心库
    import React from 'react'
    //引入ReactDOM
    import ReactDOM from 'react-dom'
    //引入App
    import App from './App'
    //
    ReactDOM.render(<App/>,document.getElementById('root'))
  • src/setupProxy.js

    const proxy = require('http-proxy-middleware')
    //
    module.exports = function(app){
    app.use(
        proxy('/api1',{ //遇见/api1前缀的请求,就会触发该代理配置
            target:'http://localhost:5000', //请求转发给谁
            changeOrigin:true,//控制服务器收到的请求头中Host的值
            pathRewrite:{'^/api1':''} //重写请求路径(必须)
        })
    )
    }
  • src/components/List/index.css

    .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%;
    }
  • src/components/List/index.jsx

    import React, { Component } from 'react'
    import PubSub from 'pubsub-js'
    import './index.css'
    //
    export default class List extends Component {
    
    state = { //初始化状态
        users:[], //users初始值为数组
        isFirst:true, //是否为第一次打开页面
        isLoading:false,//标识是否处于加载中
        err:'',//存储请求相关的错误信息
    }
    
    componentDidMount(){
        this.token = PubSub.subscribe('atguigu',(_,stateObj)=>{
            this.setState(stateObj)
        })
    }
    
    componentWillUnmount(){
        PubSub.unsubscribe(this.token)
    }
    
    render() {
        const {users,isFirst,isLoading,err} = this.state
        return (
            <div className="row">
                {
                    isFirst ? <h2>欢迎使用,输入关键字,随后点击搜索</h2> :
                    isLoading ? <h2>Loading......</h2> :
                    err ? <h2 style={{color:'red'}}>{err}</h2> :
                    users.map((userObj)=>{
                        return (
                            <div key={userObj.id} className="card">
                                <a rel="noreferrer" href={userObj.html_url} target="_blank">
                                    <img alt="head_portrait" src={userObj.avatar_url} style={{width:'100px'}}/>
                                </a>
                                <p className="card-text">{userObj.login}</p>
                            </div>
                        )
                    })
                }
            </div>
        )
    }
    }
  • src/components/Search/index.jsx

    import React, { Component } from 'react'
    import PubSub from 'pubsub-js'
    // import axios from 'axios'
    //
    export default class Search extends Component {
    
    search = async()=>{
        //获取用户的输入(连续解构赋值+重命名)
        const {keyWordElement:{value:keyWord}} = this
        //发送请求前通知List更新状态
        PubSub.publish('atguigu',{isFirst:false,isLoading:true})
        //#region 发送网络请求---使用axios发送
        /* axios.get(`/api1/search/users2?q=${keyWord}`).then(
            response => {
                //请求成功后通知List更新状态
                PubSub.publish('atguigu',{isLoading:false,users:response.data.items})
            },
            error => {
                //请求失败后通知App更新状态
                PubSub.publish('atguigu',{isLoading:false,err:error.message})
            }
        ) */
        //#endregion
    
        //发送网络请求---使用fetch发送(未优化)
        /* fetch(`/api1/search/users2?q=${keyWord}`).then(
            response => {
                console.log('联系服务器成功了');
                return response.json()
            },
            error => {
                console.log('联系服务器失败了',error);
                return new Promise(()=>{})
            }
        ).then(
            response => {console.log('获取数据成功了',response);},
            error => {console.log('获取数据失败了',error);}
        ) */
    
        //发送网络请求---使用fetch发送(优化)
        try {
            const response= await fetch(`/api1/search/users2?q=${keyWord}`)
            const data = await response.json()
            console.log(data);
            PubSub.publish('atguigu',{isLoading:false,users:data.items})
        } catch (error) {
            console.log('请求出错',error);
            PubSub.publish('atguigu',{isLoading:false,err:error.message})
        }
    }
    
    render() {
        return (
            <section className="jumbotron">
                <h3 className="jumbotron-heading">搜索github用户</h3>
                <div>
                    <input ref={c => this.keyWordElement = c} type="text" placeholder="输入关键词点击搜索"/> 
                    <button onClick={this.search}>搜索</button>
                </div>
            </section>
        )
    }
    }

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注