此部分需要预备技术栈:ajax、Axios
1 React中配置代理(proxy
)
跨域是请求发送了,但是响应被拦截了
产生跨域的本质是浏览器ajax引擎把响应拦截了,而代理服务器(中间人)没有ajax引擎;代理服务器是通过请求转发的方式
方式一
简单代理
:在package.json
中追加如下配置:"proxy":http://localhost:5000
PS:发送get的时候去找http://localhost:3000
,再写5000的话还是会产生跨域。配置了如上代理,并不是所有请求都转发给5000,而是你请求时用http://localhost:3000
进行请求,当其在3000
端口中找不到资源时将会自动转发至5000
端口进行请求,不产生跨域问题(先在3000找,找不到了转发给5000)
优点:配置简单,前端请求资源时可以不加任何前缀
缺点:不能配置多个代理
工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
方法二: 在src下创建配置文件:
src/setupProxy.js
PS:必须是这个文件名,react项目运行的时候会自动查找这个文件,并将其加入webpack的配置中,所以当你修改此文件后,你需要重新启动项目
优点:可以配置多个代理,可以灵活的控制请求是否走代理
缺点:配置繁琐,前端请求资源时必须加前缀
//代码示例CJS
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,欺骗一下服务器是从5000发的而不是3000发的
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
changeOrigin默认值为false,但我们一般将changeOrigin值设为true
*/
pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
}),
proxy('/api2', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: {'^/api2': ''}
})
)
}
2 GitHub搜索案例
ES6小知识点:
连续赋值解构
+ 重命名
let obj = {a:{b:1}}
const {a} = obj; //传统解构赋值
const {a:{b}} = obj; //连续解构赋值
const {a:{b:value}} = obj; //连续解构赋值+重命名
console.log(value)
注意:新的React中setupProxy.js文件里要这样写:
//不是const proxy = require('http-proxy-middleware')
const { createProxyMiddleware } = require('http-proxy-middleware')
module.exports = function (app) {
app.use(
createProxyMiddleware('/api1', {
target: 'http://localhost:5000',
changeOrigin: true,
pathRewrite: { '^/api1': '' }
})
)
}
Search获取数据,List需要用数据,但是他们俩是兄弟关系,所以search先把数据给App(这就需要App当年给search一个函数),然后App再把数据给List
App.js
export default class App extends Component {
//初始化状态
state = { users: [] }
//状态在哪,操作状态的方法就在哪
saveUsers = (users) => {
this.setState({ users: users })
}
render() {
return (
<div className="container">
{/* 父给子提前传个函数 saveUsers*/}
<Search saveUsers={this.saveUsers} />
{/* 自己拿着没用,转手交给儿子 */}
<List users={this.state.users} />
</div>
)
}
}
Search.js
export default class Search extends Component {
search = () => {
//获取用户的输入
//连续解构赋值写法+重命名
const { keyWordNode: { value: keyWord } } = this
//发送网络请求
axios.get(`http://localhost:3000/api1/search/users?q=${keyWord}`).then(
response => {
this.props.saveUsers(response.data.items)
},
error => { 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>
)
}
}
List.js
export default class List extends Component {
render() {
return (
<div className="row">
{
this.props.users.map((userObj) => {
return (
<div className="card" key={userObj.id}>
<a href={userObj.html_url} target="_blank" rel="noreferrer">
<img alt='head_portrait' src={userObj.avatar_url} style={{ width: '100px' }} />
</a>
<p className="card-text">{userObj.login}</p>
</div>
)
})
}
</div>
)
}
}
List除了要展示Users,还要展示:欢迎页面,加载页面,错误信息(状态驱动页面展示,所以提前准备)
完善版本的代码展示:
//App.js
export default class App extends Component {
//初始化状态
state = {
//初始化数组
users: [],
//是否第一次打开页面
isFirst: true,
//是否加载中,发送请求前调为true
isLoading: false,
//错误休息
err: ''
}
//状态在哪,操作状态的方法就在哪,更新App的state
updateAppState = (stateObj) => {
this.setState(stateObj)
}
render() {
return (
<div className="container">
<Search updateAppState={this.updateAppState} />
<List {...this.state} />
</div>
)
}
}
//Search.js
search = () => {
//获取用户的输入
//连续解构赋值写法+重命名
const { keyWordNode: { value: keyWord } } = this
//发请求前通知App更新状态
this.props.updateAppState({ isFirst: false, isLoading: true })
//发送网络请求
axios.get(`http://localhost:3000/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 })
}
)
}
//List.js注意写表达式!
export default class List extends Component {
render() {
const { isFirst, isLoading, err } = this.props
return (
<div className="row">
{
isFirst ? <h2 className='welcome'>欢迎进入鱼仔的GitHub搜索小案例,请输入关键字点击搜索</h2> :
isLoading ? <h2 className='loading'>鱼仔拼命地帮您加载中!!!请稍后!!!</h2> :
err ? <h2 className='error'>{err}</h2> :
this.props.users.map((userObj) => {
return (
<div className="card" key={userObj.id}>
<a href={userObj.html_url} target="_blank" rel="noreferrer">
<img alt='head_portrait' src={userObj.avatar_url} style={{ width: '100px' }} />
</a>
<p className="card-text">{userObj.login}</p>
</div>
)
})
}
</div >
)
}
}
3 消息订阅与发布机制
适用于任意组件间通信
使用的工具库: PubSubJS
安装:npm i pubsub-js
GitHub搜索案例的消息订阅与发布版本
//App.js
export default class App extends Component {
render() {
return (
<div className="container">
<Search />
<List />
</div>
)
}
}
//Search组件发布消息,把数据带给List
search = () => {
//获取用户的输入
//连续解构赋值写法+重命名
const { keyWordNode: { value: keyWord } } = this
//发请求前通知List更新状态
PubSub.publish('getData', { isFirst: false, isLoading: true })
//发送网络请求
axios.get(`http://localhost:3000/api1/search/users?q=${keyWord}`).then(
response => {
//请求成功后通知List更新状态
PubSub.publish('getData', { isLoading: false, users: response.data.items })
},
error => {
//请求失败后通知List更新状态
PubSub.publish('getData', { isLoading: false, err: error.message })
}
)
}
//List订阅消息,收到数据
//初始化状态--放在List中
state = {
//初始化数组
users: [],
//是否第一次打开页面
isFirst: true,
//是否加载中,发送请求前调为true
isLoading: false,
//错误休息
err: ''
}
componentDidMount() {
this.token = PubSub.subscribe('getData', (msg, stateObj) => {
this.setState(stateObj)
})
}
componentWillUnmount() {
PubSub.unsubscribe(this.token)
}
//注意这两处不是在propd中取了,是在state
const { isFirst, isLoading, err } = this.state
this.state.users.map
4 fetch
发送请求
复习Ajax
xhr:最原始的,没有封装(内置)
jQuery:封装好的api,可能会产生回调地狱(对xhr的封装)
axios:没有回调地狱(对xhr的封装)
fetch:关注分离
的设计思想(就是不一下把数据给你),浏览器原生 AJAX 接口,老版本浏览器可能不支持
fetch发送请求:(联系服务器成功和服务器响应不响应数据是两件事)
- 第一步,先联系服务器,联系上了再拿数据,此时数据还没取出来(可以直接浏览器断网联系服务器失败),建立联系
- 第二步,response的原型对象身上有json()函数,response.json()返回的是一个Promise实例,想拿的数据就在这个Promise实例对象身上,联系服务器成功及获取数据也成功,那么这个promise的状态就是成功而且保存你想要的数据,如果联系服务器成功了但获取数据失败了,那promise就是失败的状态,里面保存失败的原因
知识:第一个.then只会返回成功或失败其中一个,.then成功或失败的返回值是一个非promise值,那么.then返回的promise实例状态就是成功的,值就是这个非promise值;如果成功或失败的返回值是一个promise值,那么就作为.then返回的promise实例的值
总结就是如果直接使用
fetch
,返回的并不是直接的结果它只是一个HTTP响应
,而不是真的数据
想要获取数据,方法有二:
- 使用promise的链式调用,再第一个then中将其返回,再下个then中在使用
- 使用async+await获取
代码示例:
----------------------------- 未优化:使用then链式调用 ---------------------------------------------------------
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(`/api1/search/users2?q=${keyWord}`).then(
response => {
console.log('联系服务器成功了');
return response.json()
},
).then(
response => { console.log('获取数据成功了', response); },
).catch(
error => { console.log('获取数据失败了', error); }
)
----------------------------- 再优化后:使用async+await ---------------------------------------------------------
try {
const response= await fetch(`/api1/search/users2?q=${keyWord}`)
const data = await response.json()
console.log(data);
} catch (error) {
onsole.log('请求出错',error);
}
}