React全栈项目——京东商城

本文详细介绍了使用React进行全栈开发的实践经验,包括页面组件如navTop、Swiper、Footernav、List和Sort组件的实现,以及Goods组件和Shoppingcar组件的功能。通过前后端分离,利用Express、MongoDB、Ajax和Flex布局,实现了导航、轮播图、底部导航栏、商品列表、商品详情和购物车等功能。同时,文章还涉及了状态管理和数据交互,如利用context进行属性的提升与下放。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >



前言

经过一段时间的学习终于算是对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>
  )
  
}
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值