- 以下代码示例的参考代码只提供源码文件夹
/src
目录下的文件。 - 静态资源文件夹因为都一样,所以就不提供了,可以点击这里下载
public
目录。 - *代码的使用:根据步骤3.1.2执行完之后,替换掉相关文件夹即可。
4.1. 理解
4.1.1. 前置说明
- React本身只关注于界面, 并不包含发送ajax请求的代码
- 前端应用需要通过ajax请求与后台进行交互(json数据)
- react应用中需要集成第三方ajax库(或自己封装)
4.1.2. 常用的ajax请求库
- jQuery: 比较重, 如果需要另外引入不建议使用
- axios: 轻量级, 建议使用
1) 封装XmlHttpRequest对象的ajax
2) promise风格
3) 可以用在浏览器端和node服务器端
4.2. axios
4.2.1. 文档
https://github.com/axios/axios
4.2.2. 相关API
-
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); });
-
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"
说明:
- 优点:配置简单,前端请求资源时可以不加任何前缀。
- 缺点:不能配置多个代理。
- 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
方法二
-
第一步:创建代理配置文件
在src下创建配置文件:src/setupProxy.js
-
编写
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': ''} }) ) }
说明:
- 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
- 缺点:配置繁琐,前端请求资源时必须加前缀。
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. 消息订阅-发布机制
- 工具库: PubSubJS
- 下载: npm install pubsub-js –save
- 使用:
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. 文档
4.5.2. 特点
- fetch: 原生函数,不再使用XmlHttpRequest对象提交ajax请求
- 老版本浏览器可能不支持
4.5.3. 相关API
-
GET请求
fetch(url).then(function(response) { return response.json() }).then(function(data) { console.log(data) }).catch(function(e) { console.log(e) });
-
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> ) } }