手摸手教你:入门级React仿小红书首页

image.png

前言

React介绍

  React是一个用于构建用户界面的JavaScript库,主要用于构建UI。React拥有较高的性能以及很多特点,比如JSX(这是JavaScript的扩展语法)和组件。组件化思想在React中的应用十分广泛,通过构造组件可以将代码很好的得到复用。此外,React的声明式设计和通过对DOM的模拟,给它带来了高效灵活的特点。

项目描述

  小红书作为一款定位于社交 && 电商 的App,近些年来越来越受到更多人的关注,它的用户群体也在不断扩大。作为一名小红书的使用者,以及结合近期正在学习React相关知识,便有了使用React仿小红书首页的想法。此项目实现小红书首页的基本页面,接下来就带你进入项目展示及实战。

成品展示

话不多说,直接上图

image.png


搞错了,重来

QQ录屏20220705164119.gif


项目实战

开始阶段

  在Vscode中创建一个React项目,创建项目很多种方式,使用官方脚手架create-react-app或者Webpack创建,在此我使用的是Vitejs方式创建,这种方式会比其他方式快很多。如果大家对此感兴趣的话,后面会写一篇关于Vite和Webpack的文章,大家的点赞与支持,是我不断创作的动力来源 ღ( ´・ᴗ・` )

文件夹配置

  项目创建好后,接下来就是将相关文件中的代码以及相关多余的文件清理干净,然后创建好项目中最基本的文件夹。

  • api文件夹  用于网络请求方面
  • assets文件夹  用于存放部分图标图片等静态资源
  • pages文件夹  用于页面
  • commpents文件夹  用于可复用的组件

  更详细的文件目录如下:

image.png

依赖安装

  相关文件夹配置完成后,可以开始安装项目需要的依赖

  • npm i axios   用于获取后端数据
  • npm i react-router react-router-dom    用于项目路由跳转
  • npm i styled-components   用于实现Css in JS
  • npm i antd-mobile   一个好用的UI组件库
  • npm i classname   用于添加多个类名

  安装成功后如下所示:

image.png

组件及页面分析

路由介绍

  在以上工作完成后,接下来就是搭建相关路由了,路由在项目中是十分重要的。Route【路由】可以理解为现实中路由器后面的接口,Routes【路由器】可以理解为现实的路由器用来管理路由。在此项目中,我设置了如下几个路由:

const Home = lazy(() => import('./pages/Home'))
const Mine = lazy(() => import('./pages/Mine'))
const Message = lazy(() => import('./pages/Message'))
const Shop = lazy(() => import('./pages/Shop'))
const Choose = lazy(() => import('./pages/Choose'))

<Routes>
        <Route path="/" element={<Home/>}></Route>
        <Route path="/home" element={<Home/>}></Route>
        <Route path="/mine" element={<Mine/>}></Route>
        <Route path="/message" element={<Message/>}></Route>
        <Route path="/shop" element={<Shop/>}></Route>
        <Route path='/choose' element={<Choose />}></Route>
</Routes>

  路由搭建需要注意以下几点:

  • main.jsx中引入{BrowserRouter}方能正常使用
  • 使用路由懒加载,可提升加载速度,需引入{Suspense}

image.png

  • 若出现某个页面跳转后不显示底部导航栏,可以通过简单的条件判断和{useLocation}接受传来的值来实现。比如如下所示:
import {useLocation} from 'react-router-dom'

const {pathname}=useLocation()
if (pathname == '/choose') return

数据请求

  在此项目中采用的是fastmock来请求与管理数据,然后在api文件夹中创建request.js来获取数据。后续在相关页面中引入即可。

image.png

import axios from 'axios'

export const Getlist = () =>   
axios.get
('https://www.fastmock.site/mock/33e7fec4e60b54344eaa2c59a55b379d/red_book/red_book/list')
export const Getfood = () =>   
axios.get
('https://www.fastmock.site/mock/33e7fec4e60b54344eaa2c59a55b379d/red_book/red_book/food')

底部导航栏

QQ录屏20220705171130.gif


  底部导航栏通过路由的切换来实现。上面已经讲过一些路由知识,在此就不做过多描述。在components文件夹中创建Footer组件,用于底部导航栏。在此注意点击中间加号跳转后底部导航栏不会一起跳转,因此需要做相关判断来实现跳转新页面,源码如下:
Footer.jsx

import React from 'react'
import {Link,useLocation} from 'react-router-dom'
import {FooterWrapper} from './style' 
import classnames from 'classnames'
import { Badge } from 'antd-mobile'

function Footer(props) {
  const {pathname}=useLocation()
  if (pathname == '/choose') return
  return (
    <FooterWrapper>
      <Link to="/" className={classnames({active:pathname == '/'})}>
      <span>首页</span>
      </Link>
      <Link to="/shop" className={classnames({active:pathname == '/shop'})}>
      <span>商城</span>
      </Link>
      <Link to="/choose" className={classnames({active:pathname == '/choose'})}>
      <span><i className="iconfont icon-Addtianjia3"></i></span>
      </Link>
      <Link to="/message" className={classnames({active:pathname == '/message'})}>
      <Badge content='5'><span>消息</span></Badge>
      </Link>
      <Link to="/mine" className={classnames({active:pathname == '/mine'})}>
      <span>我</span></Link>
    </FooterWrapper>
  )
}

export default Footer

  主页面引入Footer,部分源码如下:

<>
<Routes>
        <Route path="/" element={<Home/>}></Route>
        <Route path="/home" element={<Home/>}></Route>
        <Route path="/mine" element={<Mine/>}></Route>
        <Route path="/message" element={<Message/>}></Route>
        <Route path="/shop" element={<Shop/>}></Route>
        <Route path='/choose' element={<Choose />}></Route>
</Routes>
<Footer />
</>

顶部导航栏

QQ录屏20220705171330.gif


  顶部导航栏通过Tab栏来实现内容的切换。此处有两处导航栏,在发现页面下也有一处导航栏,因此需要实现两次导航栏效果。两次导航栏实现十分简单,源码如下:
Home .jsx

import React, { useState,useEffect } from "react";
import { Tabs,Popup } from 'antd-mobile'
import Care from './Care'
import Find from './Find'
import City from './City'
import Search from '../Search'
import Daily from '../Daily'
import './style.css'

 const Home = () =>{
    const [visible1, setVisible1] = useState(false)
    const [visible2, setVisible2] = useState(false)
    return(
        <div>
            <Tabs defaultActiveKey='find' style={
                {'--active-line-color':'#ed2b43','--active-title-color':'#000000'}}>
                <Tabs.Tab title='关注' key='care'>
                    <Care/>
                </Tabs.Tab>
                <Tabs.Tab title='发现' key='find'>
                    <Find/>
                </Tabs.Tab>
                <Tabs.Tab title='城市' key='city'>
                    <City/>
                </Tabs.Tab>
            </Tabs> 
            
            <span
              onClick={() => {
                setVisible1(true)
              }}
            >
              <i className="iconfont icon-yuzhouxingqiu-12"></i>
            </span>
            <Popup
              visible={visible1}
              onMaskClick={() => {
                setVisible1(false)
              }}
              position='left'
              bodyStyle={{ height: '100%',width:'100%' }}
            >
              {<Daily />}
            </Popup>
            <span
              onClick={() => {
                setVisible2(true)
              }}
            >
              <i className="iconfont icon-sousuo"></i>
            </span>
            <Popup
              visible={visible2}
              onMaskClick={() => {
                setVisible2(false)
              }}
              position='right'
              bodyStyle={{ height: '100%',width:'100%' }}
            >
              {<Search />}
            </Popup>
        </div>
    )
 }

 export default Home

Find.jsx

import React, { useState,useEffect } from "react";
import { Tabs,Collapse,SpinLoading } from 'antd-mobile'
import Recommend from '../../Recommend'
import Video from '../../Video'
import Liver from '../../Liver'
import Food from '../../Food'
import { Getlist } from '../../../api/request'
import { Getfood } from "../../../api/request"
import './style.css'

 const Find = () =>{

  const [list,SetList] = useState([])
    useEffect(()=>{
      (async() => {
        let { data } = await Getlist()
        SetList(data)
      })()
    },[])
    const [food,SetFood] = useState([])
    useEffect(()=>{
      (async() => {
        let { data } = await Getfood()
        SetFood(data)
      })()
    },[])

    return(
        <div>
        <Tabs defaultActiveKey='1' style={
          {'--title-font-size':'15px','--active-line-height':'0px',
          '--active-title-color':'#ed2b43'}}>
          <Tabs.Tab title='推荐' key='1'>
            {list.map(item=>(
              <Recommend source={item} key={item.id}/>
            ))
              }
          </Tabs.Tab>
          <Tabs.Tab title='视频' key='2'>
            <Video/>
          </Tabs.Tab>
          <Tabs.Tab title='直播' key='3'>
            <Liver />
          </Tabs.Tab>
          <Tabs.Tab title='美食' key='4'>
            {food.map(item=>(
              <Food entry={item} key={item.id}/>
            ))
              }
          </Tabs.Tab>
          <Tabs.Tab title='知识科普' key='5'>
           .........
          </Tabs.Tab>
           .........
            此后代码大致相同,便在此处省略
        </Tabs>
        <SpinLoading style={{'--color':'#ed2b43'}} className="load"/>     
        </div>
    )
 }

 export default Find

  本项目中Tab栏使用的是antd-mobile里封装好的Tab栏,参考antd-mobile里的文档,然后引入相关组件,即可使用。虽然用起来比较方便,但是因为是已经封装好的组件,对于一些样式的修改会比较局限。若要修改样式,可以参考文档对其相关样式进行修改或者加入className类名重新定义相关样式。   下面附上相关网址,感兴趣的朋友可以参考相关文档,这是一个不错的UI组件库。
Ant Design Mobile - Ant Design Mobile

首页内容

QQ录屏20220705164119.gif


  首页是此项目的主页面,进入此页面后,可以实现页面跳转等相关功能,页面上的数据存储在fastmock中。然后在Recommend和Food页面中通过遍历将所需数据获取在页面上。此外,跳转到搜索页面,里面采用的搜索框也是antd-mobile里封装好的Search,对样式稍作修改然后直接调用即可。对于图标,采用iconfont上的图标足以满足大部分需求。在assets文件夹中引入提前加入好图标的font文件夹,此后也能方便地在font文件夹中修改图标样式。部分源码如下:

Search.jsx

import React from "react";
import { NavBar,SearchBar } from "antd-mobile";
import {Link} from "react-router-dom"
import './style.css'

 const Search = () =>{
    return(
        <div>
            <Link to='/shop'>
           <NavBar>
                <SearchBar 
                className="searchbar"
                placeholder='请输入内容'
                style={{
                    '--border-radius': '100px',
                    '--background': '#f5f5f5',
                    '--height': '30px',
                    '--placeholder-color':'#afafaf',    
                }}
                />
                <span className="search">搜索</span>
            </NavBar>
            </Link>
        </div>
    )
 }

 export default Search

Recommend.jsx

import React from "react";
import './style.css'

 const Recommend = ({source}) =>{
    const {img,content,author,icon} = source  
    return(
        <div className="List">
          <img src={img} className="list-img" />
          <div className="list-content">{content}</div>
          <div className="list-author">
            <img src={icon} className="icon-img"></img>
            <div className="list-h">{author}</div>
            <i className="iconfont icon-dianzan"></i>
          </div>
        </div>
    )
 }

 export default Recommend

Food.jsx

import React from "react";
import './style.css'

 const Food = ({entry}) =>{
    const {img,content,author,icon} = entry  
    return(
        <div className="List">
          <img src={img} className="list-img" />
          <div className="list-content">{content}</div>
          <div className="list-author">
            <img src={icon} className="icon-img"></img>
            <div className="list-h">{author}</div>
            <i className="iconfont icon-dianzan"></i>
          </div>
        </div>
    )
 }

 export default Food

总结

  这是初步搭建的项目,里面有许多功能以及相关组件还未完全实现,后期随着对React知识的深入学习与掌握,会将此项目加以修改和完善,并继续以此发文。如有优化或者错误的地方,欢迎各位大佬在评论区里指出。最后,如果此篇文章能给大家带来帮助的话,谢谢各位的点赞 ღ( ´・ᴗ・` )

猜你喜欢

转载自juejin.im/post/7116817540055040013