文章目录
前言
经过一段时间的学习终于算是对React有了一定的了解,今天试着写了下一个项目,不得不感慨一句,学习的最好方式还得是实践。废话不多说,开始实现项目。
类似于之前实现的博客项目,我们依然采用mongodb存储相关数据,根据react进行组件式开发,并且利用express实现前后端分离,flex实现页面布局
整体设计
一、页面组件构成
1.navTop(页头导航栏)
功能:进行历史记录的跳转,并根据页面不同,所展示的文字也不同,在首页时没有返回按钮,在其它页面时有返回按钮,可退回至上次浏览的页面
实现思路:属于一个路由的高阶组件封装,因为跳转后的页面与该导航栏有关联,所以要在Route里利用children渲染出该组件。
文字内容:因为该导航栏所显示文字与当前所在页面有关系,所以利用location.pathname封装出一个判断函数,进行判断,从而渲染出对应的文字。
按钮:渲染此按钮要判断当前所处位置是否为首页,不是则渲染,否则不渲染。
代码
class NavTop extends Component{
panduan(e)
{
var pageName=''
if(e=='/'){
pageName='首页'
}
else if(e=='/sort')
{
pageName='商品列表'
}
else if(e=='/shoppingcar')
{
pageName='购物车'
}
else{
pageName='商品详情'
}
return pageName
}
render()
{
return(
// getProps为跳转路由参数的传递
<Route path='/' children={(getProps)=>{
// 判断是否为首页
var {pathname}=getProps.location
var isRenderButton=pathname==='/'?false:true
return(
<div className='navTop'>
{/* 点击后退按钮,直接运用getProps.history.goBack函数 不带括号为立即执行 */}
{isRenderButton?(<button className='changeButton' onClick={getProps.history.goBack}> {'<'} </button>):'' }
<p className='pageTitle'> {this.panduan(pathname)}</p>
</div>
)}}></Route>
)
}
}
相关知识点:
①当页面跳转按钮与当前页面有关系时利用Route 的children渲染组件。
②location.pathname可以追踪当前页面所处位置(跳转位置)
③常利用三元运算符渲染组件
④onclick函数中调用的函数不带括号为立即执行函数
2.Swiper(轮播图+前后端分离)
功能:实现点击对应按钮跳转,并自动轮播。
实现思路:采用前后端分离:
后端:将数据存于mongodb中,调用4000接口,拉取数据,发送至前端。
前端:发起ajax请求,拉取响应接口数据,用数据渲染页面。
轮播部分:为组件设置state:{index,imgsArr}其中index标识当前展示图片索引,imgsArr存放拉取的数据,拉拉取到数据后对此进行更新。封装一个gonext方法对index值进行改变,并更新state中的index。设置定时器,调用此方法。渲染部分,就利用简单的map方法渲染即可,渲染时候检查index与我们的map中key是否相等,相等则将它类名设置加上active(提前写好该样式为 透明度为1 zindex为999)否则为普通(样式为透明度0 zindex为0),在渲染小圆点按钮时,为它添加一个onclick事件,点击就利用它的key值更新state中的index。
代码
class Swiper extends Component
{
constructor(props)
{
super(props)
this.state=
{
imgsArr:[],
imgListArr:[]
}
}
_goNext()
{
let index=this.state.index
if(index==this.state.imgsArr.length-1)
{
index=0
}
else{
index++
}
this.setState(
{
index:index
}
)
}
_goPre()
{
let index=this.state.index
if(index==0)
{
index=this.state.imgsArr.length-1
}
else{
index--
}
this.setState(
{
index:index
}
)
}
componentWillMount()
{
$.ajax(
{
type:'GET',
url:'http://localhost:4000/imgs',
data:{},
success:(res)=>
{
this.setState({
imgsArr:res,
index:0
})
}
}
)
setInterval(()=>
{
this._goNext()
},3000)
}
render()
{
return(
<div className='home'>
<div className='wrap'>
<ul className='pic-list'>
{
this.state.imgsArr.map((value,key)=>
{
return(
<li key={key} className={key==this.state.index?'pic-list active':'pic-list'} >
<img src={value.swiperImgSrc} alt=""/>
</li>
)
})
}
</ul>
<ul className='point-list'>
{
this.state.imgsArr.map((value,key)=>
{
return(
<li key={key} onClick={()=>{this.setState({index:key})}} className={key==this.state.index?'point-list-item active':'point-list-item'} ></li>
)
})
}
</ul>
</div>
</div>
)
}
}
相关知识点:
①巧妙利用key可以实现轮播图
②先设置好样式后添加类名使渲染更简洁
③ajax和setinterval都设置在componentWillMount()周期函数内
3.Footernav(底部导航栏+前后端分离)
功能:实现点击对应按钮跳转,对应页面图标为红色。
实现思路:采用前后端分离:
后端:将数据存于mongodb中,调用4000接口,拉取数据,发送至前端。
前端:发起ajax请求,拉取响应接口数据,用数据渲染页面。
跳转部分:为组件设置state:{data,}data存放拉取的数据,类似与顶部导航栏的实现,依旧是一个高阶组件封装——自定义路由,所以要在Route里利用children渲染出该组件。并且每一项又是一个Link点击可以跳转到相应页面,并且对于每一个Link渲染,利用match来进行判断它是否相匹配,匹配图标为红,否则为灰。
代码
class Footernav extends Component {
constructor() {
super();
this.state = {
data:[]
};
}
componentDidMount()
{
$.ajax(
{
type:'GET',
url:'http://localhost:4000/footerNavImgs',
success:(result)=>
{
console.log(result)
this.setState(
{
data:result
}
)
}
}
)
}
render() {
return (
<ul className="footer-nav">
{this.state.data.
map((value, key) => {
return (
<li className='footer-nav-item' key={key}>
<Route path={value.path} exact children={(e)=>{
return(
<Link className="footer-nav-link" to={value.path}>
{/* 高级封装组件 点击上就是match */}
<img src={e.match?value.activerImgSrc:value.normalImgSrc} alt=""/>
</Link>
)
}}></Route>
</li>
);
})
}
</ul>
);
}
}
相关知识点
①match返回boolean值 匹配为true否则false
3.List组件(初级版本未与sort发生联系)
1.功能:在首页轮播图下方,点击对应图标。可跳转至分类一页的对应项:如点击京东服饰,便会跳转到分类中的京东服饰一页,并拉取商品数据。
2.实现思路:
后端:查找mongodb中的相应数据发回到前端。
前端:简单的发起ajax请求拉取数据。
具体实现:(初级版本不与sort组件发生关联,单纯展示)同上,拉取数据后利用map方法渲染。
4.Sort组件
1.功能:位于页脚导航栏,点击可跳转至分类列表,在分类页面中,点击对应的类别,该类别高亮,并拉取对应的商品类别。
2.实现思路:
后端:存放数据的时候,为每件商品添加一个sort属性,拉取数据时根据此属性进行判断。
前端:设置初始state时有{leftlist[],showArr[]},其中leftlist为对应的类别描述,showArr存放后台拉取的数据。
具体实现:因为商品有不同的类别,在点击左侧列表时,不同类别,拉取的数据也不一样,所以需要封装一个filter方法:这个方法接受两个参数,一个是表明点击的列表项的索引是哪一个(index),一个是点击的列表项名(content),在函数里去发起ajax请求(post方法),把参数content作为data传至后端,后端根据这个content去数据库拉取对应类别的商品数据,将数据返回更新showArr数组;将参数index用来更新state中的index值,从而实现左侧列表对应类别高亮效果。
渲染:
首先对leftlist数组调用map方法,渲染出一个li并且拥有onClick事件,在onClick事件中,利用箭头函数调用filter方法,将value.descText和key传入。
其次渲染展示列表,对showArr数组调用map方法,渲染出一个商品信息展示列表,并且为了后续的商品详情展示页面,每个商品项还需要渲染出link标签。
代码:
class Sort extends Component{
constructor()
{
super()
this.state=
{
leftList:[{descText:'数码电器',index:0},{descText:'京东超市',index:1},{descText:'京东服饰',index:2}],
showArr:[],
}
}
// 定义接受的值
static contextTypes={
contentText:PropTypes.string,
index:PropTypes.number
}
componentWillMount()
{
var {contentText,index}=this.context
if(contentText==''){ this.filterItems('数码电器',0)}
else{ this.filterItems(contentText,index)}
}
filterItems(content,key) //筛选 去对比传入的项 项相同就是true 就加入到数组 然后更新数组
{
$.ajax(
{
type:'POST',
url:'http://localhost:4000/sortImgs',
data:{content:content},
success:(result)=>{
console.log(result)
this.setState(
{
showArr:result,
}
)
}
}
)
this.setState(
{
index:key
}
) //每次点击就将key传过来 然后修改index
render(){
return(
<div className='sort'>
<div className='search'>
<input type="text"/>
</div>
<ul className='list-left'>
{
// 每次点击 都将自己的序列号传到filter方法里 然后发起ajax请求,post 后台去数据库查找对应的信息 加到showarray中
this.state.leftList.map((value,key)=>
{
return(
<li onClick={()=>{this.filterItems(value.descText,key)}} className={key==this.state.index?'list-left-item active':'list-left-item'} key={key} >
{value.descText}
</li>
)
})
}
</ul>
<ul className='list-right'>{
this.state.showArr.map((value,key)=>
{
return(
<li className='list-right-item' key={key}>
<Link to={'/goods/'+value._id}>
<img src={value.imgSrc} alt=""/>
<p className='desc'>{value.desc}</p>
<p>{value.price}</p>
</Link>
</li>
)
})
}
</ul>
<Footernav></Footernav>
</div>
)
}
}
5.sort list关联
功能:在list中点击对应的sort项可跳转至sort,并依旧满足对应项高亮。
实现思路:根据本文最开始的各组件关系,可以知道list所属App->Home ->List而sort所属App->Sort,这里无疑涉及到状态提升与下放,我们要将list的相关属性提升到App,并在App中定义一个函数可以改变这些相关属性,改变后下放至Sort。
在这里利用context进行属性的提升下放。
首先,我们在App组件中声明即将传递的state,在getChildContext 方法中返回一个对象(即将传递的context),定义一个函数getNameIndex(contentText,index)下放至List点击时调用,更新state值,声明static childContextTypes={}对象表明传递的类型。
其次,在List组件中,接收context中的函数getNameIndex,依旧定义static childContextTypes={},并在渲染页面时,li添加onClick,调用该函数,传入value.contentText和key。
最后,在Sort组件中,接收contextType和index。因为Sort页面有两种方式:1是直接点击分类按钮,2是通过List。在componentWillMount()中,若接收到的contentText为‘’证明是点击按钮进入的该页面,就为filter()默认传一组初始值,否则就传入contentText。
相关知识点:
这里主要涉及了老版的context:
getChildContext 根组件中声明,一个函数,返回一个对象,就是context
childContextTypes 根组件中声明,指定context的结构类型,如不指定,会产生错误
contextTypes 子孙组件中声明,指定要接收的context的结构类型,可以只是context的一部分结构。contextTypes 没有定义,context将是一个空对象。
this.context 在子孙组件中通过此来获取上下文
注意:在指定结构类型时,npm install prop-types并 import PropTypes from ‘prop-types’
6.Goods组件
1.功能:展示商品详情、并能将对应商品添加到购物车,点击添加到购物车后,跳转至购物车页面。
在sort中已经提到了,我们在渲染商品时为每个商品渲染Link标签,为的就是商品详情页的展示,注意在渲染Link时,跳转地址将商品id作为参数传入。
前端:将_id=this.props.match.params._id,传到后端,查找数据库中对应的数据。
后端:数据获取、渲染同上,不再赘述。
2.具体实现:值得一提的是添加购物车,我们在该组件中声明一个方法:AddToShoppingCar(_id),在该方法中发起ajax请求,将_id作为data传递给后端,后端利用_id查询数据库中对应的有关商品信息,并将这些信息插入到数据库中的购物车商品表中,在ajax的success函数当中利用this.props.history.push(’/shoppingcar’)实现页面跳转,这里你可能会有疑问,为什么不利用link因为,如果此处利用link就会发生点击直接跳转到购物车页面,而无法进行数据存储。
3.代码
class Goods extends Component
{
constructor()
{
super()
this.state={
data:[]
}
}
componentWillMount() //即将加载的时候拉取数据
{
var _id=this.props.match.params._id
console.log(_id)
$.ajax(
{
type:'GET',
url:"http://localhost:4000/goods",
data:{_id:_id},
success:(result)=>
{
console.log(result)
this.setState(
{
data:result[0]
}
)
}
}
)
const id =this.props.match.params.id //获取参数
}
AddToShoppingCar(_id)
{
$.ajax(
{
type:'POST',
url:"http://localhost:4000/addshoppingcar",
data:{_id:_id},
success:(result)=>
{
console.log(result)
this.props.history.push('/shoppingcar')
}
}
)
}
render()
{
const {data}=this.state
return(
<div className='detail-view'>
<img className='detail-img' src={data.imgSrc} alt=""/>
<h4 className='detail-name'>{data.desc}</h4>
<p className='detail-price'>{data.price}</p>
<div className='bottom-button'>
<button className='bottom-buy'>立即购买</button>
<button className='bottom-shopping-car' onClick={()=>{this.AddToShoppingCar(data._id)}}>加入购物车</button>
</div>
</div>
)
}
}
7.Shoppingcar组件
1.功能:点击购物车按钮,可以查看购物车中的商品,并且点击对应商品,会跳转到该商品的详情展示。
2.前端、后端数据拉取实现同上
3.实现思路:该组件没有什么难点,需要注意的是,渲染时依旧带上Link标签 跳转地址要携带id参数
4.代码
class ShoppingCar extends Component
{
constructor()
{
super()
this.state={
data:[]
}
}
componentWillMount()
{
$.ajax(
{
type:'GET',
url:'http://localhost:4000/shoppingcar',
data:{},
success:(result)=>{
console.log(result)
this.setState(
{
data:result
}
)
}
}
)
}
render()
{
const {data}=this.state
return(
<div>
<ul className='shopping-car-view'>
{
data.map((value,key)=>
(
<Link key={value.id+key} to={`/detail/${value.id}`}>
<li className='list-items' id={value.id}>
<img className='item-img' src={value.imgSrc} alt=""/>
<div className='item-wrap'>
<p className='item-name'>{value.desc}</p>
<p className='item-price'>{value.price}</p>
</div>
</li>
</Link>
)
)
}
</ul>
<Footernav></Footernav>
</div>
)
}
}