React系列文章导航
而常用的Ajax请求库中,一般使用Axios。
前提:安装对应的插件:
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
,然后点击对应按钮,大家猜猜会发生什么?
5.结果如下:很明显的报错,发生跨域请求,因为我们的项目是3000端口,但是我们启动的服务是5000端口。
那么这种问题该如何解决呢?React通过脚手架配置代理来解决。
解决方案1:在package.json
文件中进行配置:
添加一行配置(注意,每个配置项之间用“,”分割,配置完后必须重启才能生效)
"proxy":"http://localhost:5000"
同时,在代码中,发起Ajax请求的端口需要改为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');
})
那么这种有两个服务器的情况下:
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
在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()
}
通过这种方式的配置,则可以处理多个跨域请求:
点击 获取汽车数据
点击 获取学生数据
前提:相关服务器下载:zd7g
启动相关服务:npm start
访问对应路径,若有数据,说明服务器搭建启动成功:
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="输入关键词点击搜索" />
<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': '' }
}),
)
}
页面效果如下:
搜索时:
结果:
一般来说,App
组件不含状态,因为App
组件本质上是一个外壳,仅仅是其他组件的一个容器而已。 也就是说,上述案例中,App
组件中不应该有这么多的状态操作,应该交给子组件来完成。因此,可以通过消息的订阅和发布功能来完成子组件之间的通信。
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组件。