前端小白学React系列之——浅仿一下炒股软件(雪球)

请添加图片描述

前言

React是用于构建用户界面的JavaScript库,React组件化一直是React项目开发的重要学习过程。最近自己也在刚开始学习React组件化开发,就打算通过写一个小项目来巩固自己的学习成果,毕竟——知为行之始,行为知之成。

组件展示

请添加图片描述
请添加图片描述

既然在现实中A股实现不了,但是在自己写的项目中还是有机会实现一下千股涨停的对吧! 😁😁😁

组件设计

项目准备

项目目录

请添加图片描述

api文件夹:用来存放与数据相关的链接,组件所有的数据资源都通过该文件夹中的request.js 获取,不在页面组件中获取,便于以后的操作管理

assets 文件夹:主要用来存放静态资源,像图片,视频之类的资源,达到静态资源的统一操作管理

pages 文件夹:用于放置路由跳转相关的页面

components 文件夹用来放置除去路由页面以外的组件

routes:管理组件的所有路由

技术栈

-   Html,CSS,JavaScript

-   axios

-   classnames

-   react/react-dom/react-router/react-router-dom

-   styled-components

-   swiper

安装依赖

```
npm i //用来安装模块到node_module目录中
npm i axios // 一个HTTP请求库,用来做数据请求 
npm i classnames // 一个简单的JavaScript工具库,可以将不同的的classname联合在一起,实现类名的复用
npm i react-dom react-router react-router-dom // 引入路由组件
npm i styled-components //是一种通过JavaScript来编写CSS样式的解决方案,即css in js
npm i swiper // 一款轻量级的轮播图插件,以移动端为主,可以快速做出一个轮播图

```

项目主要功能实现

底部导航栏高亮的实现👍

  • 实现的效果

请添加图片描述

  • 实现的代码
```
import React from 'react'
import {Link,useLocation} from 'react-router-dom'
import classnames from 'classnames'
import { FooterWrapper } from './style' 


export default function Footer() {
    const { pathname } = useLocation() 
      return (
    <div>
      <FooterWrapper>
        <Link to='/home' className={classnames({active: pathname.includes('/home') || pathname == '/' })}>
        <i className='iconfont icon-zhuye'></i>
        <span>雪球</span>
        </Link>
        <Link to='/stock' className={classnames({active:pathname.includes('/stock')})}>
        <i className='iconfont icon-gupiao'></i>
        <span>股票</span>
        </Link>
        <Link to='/fund' className={classnames({active:pathname.includes('/fund')})}>
          <i className='iconfont icon-jijin'></i>
        <span>基金</span>
        </Link>
        <Link to='/transaction' className={classnames({active:pathname.includes('/transaction')})}>
        <i className='iconfont icon-jiaoyi'></i>
        <span>交易</span>
        </Link>
      </FooterWrapper>
    </div>
  ) 
}

```
  • 根据路由的性质,Link 标签中的to属性会改变url的中pathname的值,此处还使用了classnames,在每个组件上添加一个动态的类,当地址包含设置的地址值时,将active效果施加给对应的Link
改进:尝试用NavLink标签,写法会比用classnames要方便

获取数据👍

在线接口工具fastmock,提供一个假接口进行在线模拟ajax请求,实现前端的页面数据展示。
点击前往fastmock

  • 实现代码
//请求接口
import axios from 'axios'

export const getHomeDetail = () =>
    axios.get("https://www.fastmock.site/mock/adeb272c51f4688e5e8fbb5b8991af89/detail/detail")

export const getStockDetail = () =>
    axios.get("https://www.fastmock.site/mock/adeb272c51f4688e5e8fbb5b8991af89/detail/stockdetail")


//接口数据导入
import React,{useState, useEffect } from 'react'
import { getStockDetail } from '../../../api/request'
import { Wrapper } from './style'

export default function StockCurviews() {
    const [stockDetail,setStockDetail] = useState([])
   
    useEffect(() => {
        (async () => {
        let { data } = await getStockDetail()
        setStockDetail(data)
        console.log( data )     //提前在控制台看看数据是否导入
        })()
    }, [])
    
  return (
      <div>
      <Wrapper>
        {
          stockDetail.map(  
            item => (
                <div className="father" key={item.id}>
                    <div className="detail-contentleft">
                    <div className="detail-contentleft-top">
                      <span>{item.name}</span>  
                    </div>
                    <div className="detail-contentleft-footer">
                        {item.id}
                    </div>
                    </div>
                    <div className="detail-contentmid">
                        <span>{item.curprice}</span>
                    </div>
                    <div className="detail-contentright">
                        <div className="box">
                        <span>
                        {item.increase}
                        </span>
                        </div>
                    </div>
                </div>
            )
          )
        }
        
      </Wrapper>
      </div>
  )
}

  • api目录下的request.js只负责做axios的数据请求

  • useEffect中使用async + await 实现数据导入

  • 数据导入后通过数组的map方法展示到组件中

轮播图👍

  • 实现的效果

请添加图片描述

  • 实现的代码
import React,{useEffect} from 'react'
import { Link } from 'react-router-dom'
import { HomeHeaderWrapper } from './style'
import Swiper from 'swiper'
import img from '../../../assets/images/头像.jpg' 

export default function HomeHeader() {
  let swiper = null;
  useEffect(() => {
      if (swiper) return 
      swiper = new Swiper('.home-top-bar-mid',{
        direction: 'vertical', //实现垂直方向上的轮播
        autoplay:true,
        loop:true
      }
      )
  }, [])
  let topdetail = [
    { id: 1, words:'光伏板块全面爆发,相关基金怎...'},
    { id: 2, words:'房地产板块大涨,滨江集团等多...'},
    { id: 3, words:'酿酒股持续走强,茅台青岛齐创...'},
    { id: 4, words:'基金半年考!哪些基金表现最好?'},
    { id: 5, words:'A股重回3400点!'}
]
  return (
    <div>
      <HomeHeaderWrapper>
        <div className="home-top">
          <div className="home-top-bar">
            <div className="home-top-bar-left">
                <img src={img} alt="" />
            </div>
         
            <div className="home-top-bar-mid swiper-container">
              <div className="home-top-bar-mid-input swiper-wrapper">{

                 topdetail.map(item => 
                  (  
                      <div key={item.id} className="swiper-slide">
                      <i className='iconfont icon-sousuo'></i>
                      <input type="text" placeholder={item.words} />
                      </div> 
                  )
                 
                 )
              }
                
               
              </div>
            </div>

            <div className="home-top-bar-right">
              <div className="home-top-bar-right-i">
                <i className="iconfont icon-youjian1"></i>
              </div>
            </div>
          </div>
        </div>
      </HomeHeaderWrapper>
    </div> 
  )
}

  • useEffect需要传一个[]即空数组当第二个参数,传空数组则表示轮播图的更新什么都不依赖。

  • swiper轮播实现的:固定的html结构 .swiper-container>.swiper-wrapper>.swiper-slide{n}

    改进: 这里文字轮播数据比较少,我就偷了一点小懒,没有去用fastmock去创建一个接口,把这些数据写入接口中。

模态框👍

  • 实现的效果

请添加图片描述

  • 实现的代码
//home组件
import React,{useState,useEffect} from 'react' 
...

export default function Home() {
  const [visible, setVisible] = useState(false)

  const onModalClose = () => {
    setVisible(false)
  }
  const onModalConfirm = () => {
    setVisible(false)
  }

  const navigate = useNavigate()
    useEffect(() => {
      navigate('/home/care')
      console.log('--------')
  },[])

  return (
    <div>
      <Wrapper>
      <HomeHeader/>
      <HomeNav/>
      <Outlet/> 
      <Modal 
        visible={visible}
        title="是否进入市场了解更多?"
        onClose={onModalClose}
        onConfirm={onModalConfirm}
      />
      <div className="public" onClick={() => setVisible(true)}>
        <i className='iconfont icon-jiahao'></i>
      </div>
      </Wrapper>
    </div>
    
  )
}

//Model组件
import React from 'react'
import {  useState,useEffect } from 'react'
import './modal.css' 

export default function Modal(props) {
    const [visible,setVisible] = useState(false)
    const {visible:show,children, title } = props
    const {onClose,onConfirm} = props

    useEffect(() => {
        setVisible(show)
        console.log(show)
    },[show])
    
    const closeModal = () => {
        console.log('想要关闭吗?')
        setVisible(false)
        // onClose && 
        onClose()
    }
    const confirm = () => {
        console.log('想要确定吗?')
        setVisible(false)
        // onConfirm && 
        onConfirm()
    }
    const maskClick = () => {
        setVisible(false)
        //  onClose && 
         onClose()
    }

    return (
        visible && <div className="modal-wrapper">
        <div className="modal">
            <div className="modal-title">{title}</div>
            <div className="modal-content">{children}</div>
            <div className="modal-operator">
                <button 
                    onClick={closeModal}
                    className="modal-operator-close">取消</button>
                <button 
                    onClick={confirm}
                    className="modal-operator-confirm">确定</button>
            </div>
        </div>
        <div className="mask" onClick={maskClick}></div>
    </div>
    )
}

  • 父子组价传值:父组件向子组件传值,通过props,将父组件的state传递给了子组件

  • 子组件通过调用父组件传递到子组件的方法向父组件传递消息的。父组件收到参数后将值赋给父组件的state。

    父组件 => home组件 通过onModalClose(),onModalConfirm()将方法传递给子组件 => Modal组件

    子组件 => Modal 通过调用自己的closeModal() confirm() maskClick()方法去执行父组件=>home的onModalClose(),onModalConfirm()方法

  • 子组件Modal中

return (
        visible && <div className="modal-wrapper">
        <div className="modal">
            <div className="modal-title">{title}</div>
            <div className="modal-content">{children}</div>
            <div className="modal-operator">
                <button 
                    onClick={closeModal}
                    className="modal-operator-close">取消</button>
                <button 
                    onClick={confirm}
                    className="modal-operator-confirm">确定</button>
            </div>
        </div>
        <div className="mask" onClick={maskClick}></div>
    </div>
    )

只有当visble为true时,模态框才得以真正体现

二级路由实现👍

  • 实现的效果

请添加图片描述

  • 实现的代码
//Stock主页面下的index.jsx
import React,{useEffect} from 'react'
// import StockDetail from './StockDetail'
import StockHead from './StockHead'
import StockNav from './StockNav'
import { Outlet,useNavigate } from 'react-router'
import { Wrapper } from './style'

export default function Stock() {
  const navigate = useNavigate()
    useEffect(() => {
      navigate('/stock/curviews')
      console.log('--------')
  },[])
  return (
    <div>
      <Wrapper>

          <StockHead/>
          <StockNav/>
          ...
          <Outlet/>
      </Wrapper>
    </div>
  )
}

//routes目录下的index.jsx
import React, { lazy } from 'react'
import { Routes, Route} from 'react-router-dom'
import Home from '../pages/Home'

...
const StockCurviews = lazy(() => import('../components/Stock/StockCurviews'))
const StockBaijiu = lazy(() => import('../components/Stock/StockBaijiu'))
const StockMedicine = lazy(() => import('../components/Stock/StockMedicine'))
const StockViews = lazy(() => import('../components/Stock/StockViews'))
const StockZh = lazy(() => import('../components/Stock/StockZh'))

export default function RoutesConfig() {
  return (
    <div>
        <Routes>
            ...
            <Route path='/stock' element={<Stock/>}>
              <Route path='/stock/curviews' element={<StockCurviews/>}></Route>
              <Route path='/stock/baijiu' element={<StockBaijiu/>}></Route>
              <Route path='/stock/medicine' element={<StockMedicine/>}></Route>
              <Route path='/stock/views' element={<StockViews/>}></Route>
              <Route path='/stock/Zh' element={<StockZh/>}></Route>
            </Route>
        </Routes>
    </div>
  )
}

  • Stock主页面中的Outlet为二级路由的出口,当/routes目录下的index.jsx的Route匹配到了对应的组件,就从Outlet中渲染

  • /routes目录下的index.jsx实现了路由的懒加载:当react项目非常庞大的时候,因为需要先请求加载资源之后,才会渲染页面,这就会严重影响到页面的首批加载,如果能运用懒加载则会代码分割,按需进行加载

小结

万事开头难,但是只要开始做项目就已经成功了一半。该项目还存在着许多不足和不严谨的地方,我将在后续继续对该项目进行完善,欢迎各位大佬指正以及希望对像我这样的前端小白尽一份绵薄之力啦~😁😁😁
请添加图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值