react路由和异步请求

React 路由和异步请求
1 React 路由
1.1 为什么需要路由
在传统的 Web 应用中个,每个 URL 对应网站中的一个页面;但在 SPA(单页面应用中),
由于只有一个页面,如果要实现不同 URL 在相同页面把内容替换成不同的组件,就需要额
外的路由组件来实现了。
例如以下三个页面,头部和底部都是相同的,而中间需要根据 URL 的不同,显示不同
的组件,这时就需要路由帮忙。
商品列表  商品详情  购物车
1.2 在项目中添加路由支持
(1)在项目根部执行 npm 安装指令,安装“react-router-dom”组件
npm install react-router-dom --save-dev
1.3 简单使用路由
1.3.1 导入路由组件“BrowserRouter”并包裹根组件”<App />”
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render(
<BrowserRouter><App /></BrowserRouter>,
document.getElementById('root'));
registerServiceWorker();
1.3.2 修改根组件 App,定义不同组件封装原来静态网页的内容
把页面分割并封装成 React 组件
各组件功能如下所示:
组件名称  功能描述
Header  网页头部的导航和搜索框
Footer  页面底部的导航
ProductList  产品列表
Login  登录
Cart  购物车
ProductDetail  产品详情
这时,不用路由的话只可以用以下方式显示其中一个部件
import React, { Component } from 'react';
import Header from './components/Header';
import Footer from './components/Footer';
import ProductList from './components/ProductList';
import Cart from './components/Cart';
import Login from './components/Login';
import ProductDetail from './components/ProductDetail';
class App extends Component {
render() {
return (
<div className="pageWrapper">
<Header></Header>
<div className="pageBreak"></div>
<ProductList></ProductList>
{/*
<Cart></Cart>
<Login></Login>
<ProductDetail></ProductDetail>
*/}
<div className="pageBreak"></div>
<Footer></Footer>
</div>
);
}
}
export default App;
1.3.3 使用路由的“Switch”组件,根据 URL 地址显示不同的 React 组件
使用路由,修改 App 组件。
import React, { Component } from 'react';
import Header from './components/Header';
import Footer from './components/Footer';
import ProductList from './components/ProductList';
import Cart from './components/Cart';
import Login from './components/Login';
import ProductDetail from './components/ProductDetail';
import { Route, Switch } from 'react-router-dom';
class App extends Component {
render() {
return (
<div className="pageWrapper">
<Header></Header>
<div className="pageBreak"></div>
<Switch>
<Route path="/cart" component={Cart} />
<Route path="/product" component={ProductDetail} />
<Route path="/list" component={ProductList} />
<Route path="/login" component={Login} />
<Route path="/" component={ProductList} />
</Switch>
<div className="pageBreak"></div>
<Footer></Footer>
</div>
);
}
}
export default App;
上述代码中,值得注意的是:
(1)“<Switch>”组件用于包含一组路由组件“<Route>”,每个路由组件“<Route>”提供一个
path 属性用于指定 url,一个 component 属性用于指定自定义的 React 组件。
(2)“<Switch>”中指定的一组 React 组件中,只有一个会被绘制,Route 的 path 与当前浏
览器 url 匹配的那一个 React 组件。
(3)“<Route>”中还有一个“exact”属性,用于精确匹配 url,如果不设定精确匹配,则会匹
配所有 path 开头的 url。我们把“<Route path="/" component={ProductList} />”放到最后,
正是因为这里的 path="/"不是精确匹配,如果放在第一项,会导致其他路由失效。要精确匹
配 path= "/",应如下定义路由项。
<Route exact path="/" component={ProductList} />
经过上述修改,输入不同地址,网页中间就会显示不同的组 件
中间显示登录  中间显示购物车
1.3.4 使用路由组件“<Link>”替代<a> 修改导航链接,实现 SPA 效果
传统的“<a>”是会发生跳转的,我们使用 React 路由提供的 Link 组件替代页面中发生跳
转的“<a>”,实现 SPA。
“<Link>”的 to 属性用于指定跳转的路径,此外使用方式与传统“<a>”很类似。
import React, { Component } from 'react';
import {Link} from 'react-router-dom';
class Header extends Component {
render(){
return (
<div className="headerWrapper">
<div className="headerNavWrapper">
<Link to="/login" className="accountButton"></Link>
<Link to="/cart" className="shoppingCartButton"></Link>
<a className="mainMenuButton">&nbsp;</a>
</div>
<div className="headerLogoWrapper">
<Link to="/" className="mainLogo">
<img src="/static/images/common/mainLogo.png" alt="" />
</Link>
</div>
<div className="searchFormWrapper">
<fieldset>
<input type="text" value="" id="searchField" className="searchField
fieldWithIcon searchFieldIcon" name="shopSearchField" placeholder="搜索..." />
</fieldset>
</div>
</div>
);
}
}
export default Header;
这样,我们就可以通过页面导航 Link 实现 SPA 的跳转了。
1.4 路由参数
在的 Web 应用中,我们常常需要通过 URL 传递一些参数,以便在加载页面的时候查找
出所需要的数据。
比如上述应用中的 ProductDetail 组件,是用作加载一项商品详情的,在渲染组件前就
应该先传入商品 ID 获取特定的一种商品。
1.4.1 路径参数
(1)声明参数
React 路由允许我们通过“:参数名”的方式在路由项中声明路径参数。
在 App 组件中修改路由项,声明 id 参数。
<Route path="/productDetail/:id" component={ProductDetail} />
(2)传递参数
在通过 Link 或者其他方式发生 URL 变更时,可以传入声明好的路径参数。
在 ProductList 组件中使用 Link 改变 URL,传入参数。
import React, { Component } from 'react';
import {Link} from 'react-router-dom';
class ProductList extends Component {
render(){
return (
……
<Link to="/product/1"><img … /></Link>
……
<Link to="/product/2"><img … /></Link>
……
);
}
}
(3)在目标组件中获取参数
在被传入参数的组件中,通过“this.props.match.params.参数名”即可获取到传入的路径
参数。
import React,{Component} from 'react';
class ProductDetail extends Component{
render(){
let id = this.props.match.params.id;
console.log("id 参数值:"+id);
……
}
}
效果如下图所示
1.4.2 查询参数
传统的 Web 网站在面对参数不确定的情况下,会使用查询字符串的方式传递参数,也
就是“URL?参数 1=值 1&参数 2=值 2…”这种 get 请求的参数。
早前版本的 react 路由组件是可以通过“this.props.location.query.参数名”的方式在目标
组件中获取查询参数的;但在 react-router 4.0 之后的版本,官方去掉了 query 对象,只能
通过“this.props.location.search”获取到整个查询字符串(?及后面的整个字符串)。这种变化
可能是考虑到查询字符串的格式并没有什么官方标准,于是 react 路由打算把解释工作交给
使用者自己去处理。如果是熟悉正则表达式的话,使用正则匹配取出参数也不复杂。
这里为了简化,直接使用 npm 提供的名为“query-string”去解释查询字符串,获取参数。
具体用法可以参见 https://npm.taobao.org/package/query-string。
(1)为项目添加 query-string 组件支持
$ npm install query-string --save-dev
(2)使用查询字符串传参
查询参数与路径参数不同,路径参数是必要参数,因此需要在路由项中声明;而查询参
数是可有可无的,不要额外声明。
我们打算对 ProductList 组件传入一个名为 name 的查询参数,用于按商品名模糊查询。
http://localhost:3000/list?name=青瓷
(3)在目标组件中获取参数
import React, { Component } from 'react';
import {Link} from 'react-router-dom';
import queryString from 'query-string';
class ProductList extends Component {
render(){
console.log(this.props.location.search);
if(this.props.location.search){
let query = queryString.parse(this.props.location.search);
console.log(query);
}
return ( … );
}
}
我们可以通过浏览器控制台看你到转换前后的查询字符串对象。
1.5 使用代码修改 URL 并传递参数
很多时候,我们要通过事件代码来更新 URL,来触发其他路由项组件的显示和更新,这
时可以使用路由的“history”对象,把一个新的 URL 推送到 history 中。
路由的 history 对象要通过“this.props.history”获取,而要用到 history 对象,则需要在组
件中导入“react-router-dom”的“withRouter”对象,并用该对象包装需要导出的组件。
import React, { Component } from 'react';
import {Link, withRouter} from 'react-router-dom';
class Header extends Component {
searchProducts(e){
if(!e.target.value.trim())
return;
if(e.keyCode == 13){
this.props.history.push("/list?name="+e.target.value.trim());
}
}
render(){
return (
<div className="headerWrapper">
……
<input type="text"
onKeyUp={this.searchProducts.bind(this)}
className="searchField fieldWithIcon searchFieldIcon"
name="shopSearchField" placeholder="搜索..." />
……
</div>
);
}
}
export default withRouter(Header);
当我们在 Header 组件的文本框中输入名称并回车时,会触发 URL 的改变和参数的传
递。
更加详细的路由使用,请参考官方文档 https://www.npmjs.com/package/react-router
或相关技术文章 http://www.jianshu.com/p/e3adc9b5f75c
2 使用 fetch 获取服务器端数据
SPA 一般都采用前后端分离的开发方式。后端可以使用任何的服务器端 Web 技术,诸
如 JavaEE、PHP、Node.js、Python 等等,后端提供基于 RESTful 风格的 Web 服务,接收前
端请求并返回 JSON 格式的数据。
这里使用基于 Spring Boot 的 Spring MVC 技术提供后端服务,具体细节略去,仅在这
里描述所提供的服务接口。
URL  功能
http://localhost:9090/api/products/latest  获取最新的 4 种产品,返回 JSON 格式数据
http://localhost:9090/api/products/1  获取 id=1 的商品明细信息
http://localhost:9090/api/products?name=
青瓷
模糊查询名称中包含“青瓷”的产品信息,返
回 JSON 格式数据
具体请求效果如下图所示。
2.1 使用 Node 服务器代理后端服务请求
作为前后端分离的项目,后端和前端往往不是运行在同一个服务器中的。例如上述开发
中,后端的 JavaEE 服务是运行在 Tomcat 服务器(Spring Boot 内嵌的容器)中的,而前端
则是使用 Node.js 提供的测试服务器。前者域名为“localhost:9090”,而后者是“localhost:3000”。
这时,如果前端通过 AJAX 技术请求后端数据,就会遇到 JavaScript 请求不能跨域执行的问
题而无法请求。要解决这个问题,要么就需要使用 jsonp 协议(跨域 JSON 协议),要么就要
把前后端两个服务器通过代理服务器代理到同一个域名之下。在实际部署中,我们可以通过
Nginx 等静态资源服务器实现代理,而在开发中“create-react-app”工具包就可以直接配置后
端代理,把 lcoalhost:9090 的域名请求代理到 localhost:3000 域名之下。
修改前端 react 项目的配置文件 package.json,在其中添加如下 "proxy" 配置项。
{
……其他配置项……
"proxy":{
"/api":{
"target":"http://localhost:9090 "
}
}
}
这时,当我们在请求中访问“localhost:3000/api”就等同于我们访问了“localhost:9090/api”
了。下图显示代理后的后端请求可以通过前端服务器访问到。
2.2 前端通过 fetch 组件请求后端数据
2.2.1 fetch
上一代的 AJAX 技术中,我们是通过 XMLHttpRequest 对象请求服务器端数据的,请求
成功后通过异步回调函数渲染前端界面。著名的 jQuery 中$.ajax()封装的就是 XHR 对象。以
下显示了 XHR 的基本用法。
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'json';
xhr.onload = function() {
console.log(xhr.response);
};
xhr.onerror = function() {
console.log("Oops, error");
};
xhr.send();
这种传统的异步回调在复杂的使用环境中往往会一个接一个的嵌套,让代码陷入难以维
护的境地。新一代的JavaScript (ES6),不再建议使用XMLHttpRequest,而是用一种叫Promise
的方式组织代码,让我们不用陷入到回调的连环套中。
新一代浏览器(Chrome、Firefox 等),都支持名为 fetch API 的 AJAX 技术,可以实现非
回调式的异步请求处理。不支持 fetch 的地方,也可以通过安装 fetch polyfill(插件、垫片)
来使用 fetch。以下是使用 fetch API 的异步请求示例,异步处理是平面的,而不是嵌套的。
fetch(url).then(function(response) {
return response.json();  //把返回的数据解释为 json 对象
}).then(function(data) {
console.log(data);  //处理得到的 json 对象
}).catch(function(e) {
console.log("error"); //处理出现的错误
});
2.2.2 使用 fetch API 加载 product
现在我们要实现如下功能:当用户通过产品列表点选产品后,路由会调用 ProductDetail
组件并传入 id 参数,我们获取 id 参数,并在 ProductDetail 中显示对应的商品。具体效果如
下。
在 ProductDetail 组件中添加 findProduct 方法,根据路由参数(props.match.params.id)
获取 Product 数据,并调用 setState()重新渲染组件。
class ProductDetail extends Component{
findProduct(props){
let compo = this;
let id = props.match.params.id;
fetch("/api/products/"+id).then(function(response){
return response.json();
}).then(function(data){
compo.setState({ //异步请求后把返回的 product 重新设置到 state
product:data
})
});
}
constructor(props){
super(props);
this.findProduct = this.findProduct.bind(this);
this.state = { //构造方法中初始化 state 为空
product: { }
};
}
……
}
前面讲述的组件声明周期中,componentDidMount 方法适和用于组件创建后第一次加
载 AJAX 数据,于是我们在这里中调用 findProduct。
class ProductDetail extends Component{
……
componentDidMount(){
this.findProduct(this.props);
}
……
}
需要注意的是,在另外的一些情况下,父组件传入的 props 有可能被修改而子组件已存
在了,子组件不会再引发 componentDidMount。这时可以通过 componentWillReceiveProps
再次发出 AJAX 请求获取新的数据并更新界面。
class ProductDetail extends Component{
……
componentWillReceiveProps(nextProps){
this.findProducts(nextProps);
}
……
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值