React(六)- React解决跨域请求及订阅发布的使用

React(六)- React解决跨域请求及订阅发布的使用

  • 前言
  • 一. React解决跨域请求
    • 1.1 配置代理解决跨域问题
      • 总结1
    • 1.2 setupProxy.js解决跨域问题
      • 总结2
  • 二. 实战:Github搜索案例
    • 2.1 消息订阅和发布

React系列文章导航

前言

  1. React框架中,其本身只关注于界面,并不包含发送Ajax请求的代码。
  2. 而一个前端应用需要通过Ajax请求与后台进行交互。
  3. React应用中则需要集成一个第三方Ajax库。

而常用的Ajax请求库中,一般使用Axios。

一. React解决跨域请求

前提:安装对应的插件:

npm install axios

1.搭建一个服务器server1.js(用NodeJs编写)

const express = require('express')
const app = express()

app.use((request,response,next)=>{
     
	console.log('有人请求服务器1了');
	console.log('请求来自于',request.get('Host'));
	console.log('请求的地址',request.url);
	next()
})

app.get('/students',(request,response)=>{
     
	const students = [
		{
     id:'001',name:'tom',age:18},
		{
     id:'002',name:'jerry',age:19},
		{
     id:'003',name:'tony',age:120},
	]
	response.send(students)
})

app.listen(5000,(err)=>{
     
	if(!err) console.log('服务器1启动成功了,请求学生信息地址为:http://localhost:5000/students');
})

2.运行该服务器:

node server1.js

打印如下信息:
在这里插入图片描述

3.编写Demo1:Axios的使用

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

export default class App extends Component {
     
    getData = () => {
     
        // axios.get()获得一个Promoise实例,要向获得其成功的值和失败的原因,那么需要用then,传入两个回调
        axios.get('http://localhost:5000/students').then(
            response => {
      console.log('成功了', response.data); },
            error => {
      console.log('失败了', error); }
        )
        axios.post()
    }

    render() {
     
        return (
            <div>
                <button onClick={
     this.getData}>获取学生数据</button>
            </div>
        )
    }
}

4.启动项目:npm start,然后点击对应按钮,大家猜猜会发生什么?
React(六)- React解决跨域请求及订阅发布的使用_第1张图片
5.结果如下:很明显的报错,发生跨域请求,因为我们的项目是3000端口,但是我们启动的服务是5000端口。
在这里插入图片描述
那么这种问题该如何解决呢?React通过脚手架配置代理来解决。

1.1 配置代理解决跨域问题

解决方案1:在package.json文件中进行配置:

添加一行配置(注意,每个配置项之间用“,”分割配置完后必须重启才能生效

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

同时,在代码中,发起Ajax请求的端口需要改为3000,这样才能将这个请求转发给端口为5000的服务,如下:
React(六)- React解决跨域请求及订阅发布的使用_第2张图片
点击获取数据后:
React(六)- React解决跨域请求及订阅发布的使用_第3张图片

总结1

  • 优点:配质检单,前端请求资源的时候可以不加任何前缀。
  • 缺点:只能配置一个代理。
  • 工作方式:上述方式配置代理,当请求了3000端口不存在的资源的时候,那么该请求就会转发给5000端口。

案例2:
1.搭建一个服务器server2.js,并启动

const express = require('express')
const app = express()

app.use((request,response,next)=>{
     
	console.log('有人请求服务器2了');
	next()
})

app.get('/cars',(request,response)=>{
     
	const cars = [
		{
     id:'001',name:'奔驰',price:199},
		{
     id:'002',name:'马自达',price:109},
		{
     id:'003',name:'捷达',price:120},
	]
	response.send(cars)
})

app.listen(5001,(err)=>{
     
	if(!err) console.log('服务器2启动成功了,请求汽车信息地址为:http://localhost:5001/cars');
})

那么这种有两个服务器的情况下:

  • service1:5000端口
  • service2:5001端口

App组件添加一个获取数据的按钮:

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

export default class App extends Component {
     
    getData1 = () => {
     
        // axios.get()获得一个Promoise实例,要向获得其成功的值和失败的原因,那么需要用then,传入两个回调
        axios.get('http://localhost:3000/students').then(
            response => {
      console.log('成功了', response.data); },
            error => {
      console.log('失败了', error); }
        )
        axios.post()
    }

    getData2 = () => {
     
        // axios.get()获得一个Promoise实例,要向获得其成功的值和失败的原因,那么需要用then,传入两个回调
        axios.get('http://localhost:3000/cars').then(
            response => {
      console.log('成功了', response.data); },
            error => {
      console.log('失败了', error); }
        )
        axios.post()
    }

    render() {
     
        return (
            <div>
                <button onClick={
     this.getData1}>获取学生数据</button>
                <button onClick={
     this.getData2}>获取汽车数据</button>
            </div>
        )
    }
}

那么问题来了,我在点击获取汽车数据按钮的时候,我怎么保证请求会到5001端口而不是5000端口?更何况我在package.json文件中只能添加一个代理:
在这里插入图片描述
那么这种情况该怎么办?
解决,搭建一个脚手架文件setupProxy.js

1.2 setupProxy.js解决跨域问题

src目录下创建文件setupProxy.js

const proxy = require('http-proxy-middleware')

module.exports = function (app) {
     
    app.use(
        proxy('/api1', {
     // 遇到以 /api1 为前缀的请求,就会触发这个代理
            target: 'http://localhost:5000',// 请求转发的目标
            // 控制服务器收到的响应头中Host字段的值,默认为false 
            // 若为false,那么我这里发送一个跨域请求,在服务器端,是能够发现请求来源于跨域的地址是http://localhost:3000
            // 若为true,那么在服务器端看到的host 是http://localhost:5000
            changeOrigin: true,
            // 这个一定要写,否则,我3000端口发起的/api1/students端口 到服务器中就是/api1/students了,这样的请求是404错误
            // 而我服务器上的接口是/students,因此必须重写路径
            pathRewrite: {
      '^/api1': '' }
        }),
        proxy('/api2', {
     
            target: 'http://localhost:5001',
            changeOrigin: true,
            pathRewrite: {
      '^/api2': '' }
        })
    )
}

App组件中,添加一个前缀用来区分,比如api1.

getData1 = () => {
     
    axios.get('http://localhost:3000/api1/students').then(
        response => {
      console.log('成功了', response.data); },
        error => {
      console.log('失败了', error); }
    )
    axios.post()
}

getData2 = () => {
     
    axios.get('http://localhost:3000/api2/cars').then(
        response => {
      console.log('成功了', response.data); },
        error => {
      console.log('失败了', error); }
    )
    axios.post()
}

通过这种方式的配置,则可以处理多个跨域请求:
React(六)- React解决跨域请求及订阅发布的使用_第4张图片
点击 获取汽车数据
React(六)- React解决跨域请求及订阅发布的使用_第5张图片
点击 获取学生数据
React(六)- React解决跨域请求及订阅发布的使用_第6张图片

总结2

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

二. 实战:Github搜索案例

前提:相关服务器下载:zd7g

启动相关服务:npm start
React(六)- React解决跨域请求及订阅发布的使用_第7张图片
访问对应路径,若有数据,说明服务器搭建启动成功:
React(六)- React解决跨域请求及订阅发布的使用_第8张图片


紧接着开始编写项目:
项目结构如下:
React(六)- React解决跨域请求及订阅发布的使用_第9张图片
List组件:

Jsx文件:

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

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((user) => {
     
                                    return (
                                        <div className="card" >
                                            <a href={
     user.html_url} target="_blank" rel="noreferrer">
                                                <img alt="head_portrait" src={
     user.avatar_url} style={
     {
      width: '100px' }} />
                                            </a>
                                            <p className="card-text">{
     user.login}</p>
                                        </div>
                                    )
                                })
                }
            </div>
        )

    }
}

export default List;

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%;
  }
  

Search组件:负责把请求转发给我们启动好的5000服务器,5000服务器会去github上拉取真实的数据。

Jsx文件:

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

class Search extends Component {
     
    search = () => {
     
        // 1.获取用户输入(连续结构赋值+重命名):意思是,我从this上面拿到了keyWordNode对象
        // 并且对其中的value的属性进行一个改名,改成keyWord
        const {
      keyWordNode: {
      value: keyWord } } = this

        // 2.发送请求前通知App更新状态
        this.props.uppdateAppState({
      isFirst: false, isLoading: true })

        // 3.发送网络请求
        axios.get(`http://localhost:3000/api1/search/users?q=${
       keyWord}`).then(
            response => {
     
                // 4.请求成功后再通知App更新状态
                this.props.uppdateAppState({
      isLoading: false, users: response.data.items })
            },
            error => {
     
                // 5.请求失败后再通知App更新状态
                this.props.uppdateAppState({
      isLoading: false, err: error.message })
                console.log('失败了', error);
            }
        )
    }
    render() {
     
        return (
            <section className="jumbotron">
                <h3 className="jumbotron-heading">搜索Github用户</h3>
                <div>
                    <input ref={
     c => this.keyWordNode = c} type="text" placeholder="输入关键词点击搜索" />&nbsp;
                    <button onClick={
     this.search}>搜索</button>
                </div>
            </section>
        );
    }
}

export default Search;

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: '', //存储请求相关的错误信息
    }

    uppdateAppState = (stateObj) => {
     
        this.setState(stateObj)
    }

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

index.js入口文件

// 引入React核心库
import React from 'react'
// 引入ReactDOM
import ReactDOM from 'react-dom'
// 引入App组件
import App from './App'
// 渲染App到页面
ReactDOM.render(<App />, document.getElementById('root'))

setupProxy.js代理配置文件:

const proxy = require('http-proxy-middleware')

module.exports = function (app) {
     
    app.use(
        proxy('/api1', {
     
            target: 'http://localhost:5000',// 请求转发的目标
            changeOrigin: true,
            pathRewrite: {
      '^/api1': '' }
        }),
    )
}

页面效果如下:
React(六)- React解决跨域请求及订阅发布的使用_第10张图片
搜索时:
React(六)- React解决跨域请求及订阅发布的使用_第11张图片
结果:
React(六)- React解决跨域请求及订阅发布的使用_第12张图片
一般来说,App组件不含状态,因为App组件本质上是一个外壳,仅仅是其他组件的一个容器而已。 也就是说,上述案例中,App组件中不应该有这么多的状态操作,应该交给子组件来完成。因此,可以通过消息的订阅和发布功能来完成子组件之间的通信。


2.1 消息订阅和发布

React中要实现消息的订阅和发布,则需要使用工具库:PubSubJs

准备工作:下载对应的插件:

npm install pubsub-js --save

使用方法:

  • 引入:import PubSub from 'pubsub-js'
  • 订阅:PubSub.subscribe('delete',function(data){})
  • 发布消息:PubSub.publish('delete',data)

对上述案例作出如下更改:
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>
        )
    }
}

List组件:负责订阅对应的消息

class List extends Component {
     
    // 初始化状态
    state = {
     
        users: [], // users初始值为一个空数组
        isFirst: true, // 是否为第一次打开页面
        isLoading: false, // 标识是否处于加载中
        err: '', //存储请求相关的错误信息
    }
	// 在组件将要完成挂载的时候,开始订阅,获得对应的状态值,并进行更新
    componentDidMount() {
     
        this.token = PubSub.subscribe('test', (_, stateObj) => {
     
            this.setState(stateObj)
        })
    }
    // 解除订阅
    componentWillUnmount() {
     
        PubSub.unsubscribe(this.token)
    }

    render() {
     
        // ...代码不变
    }
}

export default List;

Search组件:负责发布对应的消息

class Search extends Component {
     
    search = () => {
     
        const {
      keyWordNode: {
      value: keyWord } } = this
        PubSub.publish('test',{
     isFirst: false, isLoading: true})

        axios.get(`http://localhost:3000/api1/search/users?q=${
       keyWord}`).then(
            response => {
     
                // 4.请求成功后再通知List更新状态
                PubSub.publish('test',{
     isLoading: false, users: response.data.items})
            },
            error => {
     
                // 5.请求失败后再通知List更新状态
                PubSub.publish('test',{
     isLoading: false, err: error.message})
                console.log('失败了', error);
            }
        )
    }
    render() {
     
        // 不变
    }
}

export default Search;

最后的页面效果跟Github搜索案例的效果是一样的,也就不展示了,这种通过发布订阅的方式完成组件间的通讯,这样的方式看起来更加好,解放App组件。

你可能感兴趣的:(React)