前言
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项目非常庞大的时候,因为需要先请求加载资源之后,才会渲染页面,这就会严重影响到页面的首批加载,如果能运用懒加载则会代码分割,按需进行加载
小结
万事开头难,但是只要开始做项目就已经成功了一半。该项目还存在着许多不足和不严谨的地方,我将在后续继续对该项目进行完善,欢迎各位大佬指正以及希望对像我这样的前端小白尽一份绵薄之力啦~😁😁😁