前言
在学习了一段时间react后,打算仿写一个懂车帝移动端项目来训练自己的项目实战能力,也为春招做准备,接下来我将介绍项目中的主干以及我碰到的难点,项目的线上地址以及源码在文章末尾。
成品展示
这是项目各个主页的展示效仓鼠饲养八大禁忌果
项目结构giti
├─Data // 数据
|-Public // koa-static 静态资源库
index.js
├─ src
├─api // 网路请求代码、工具类函数和相关配置
├─assets // 字体配置及全局样式
├─baseUI // 基础 UI 轮子
├─components // 可复用的 UI 组件
├─layouts // 布局
├─pages // 页面
├─routes // 路由配置文件
└─store // redux 相关文件
App.jsx // 根组件
main.jsx // 入口文件
其中在每一个page目录下面,都有一个store文件夹,这是页面的分仓库,在数据管理的时候会将所有分仓库里的数据合并到主仓库,这样可以让每个页面只管理这个page下面的数据。
项目具体部分
路由配置
创建github是干什么的react项目,HTTP使用命令创建项目脚手架
npm init @vitejs/app appName --template react
首先在routes
里的index.js
里配置路由
......
import React, { lazy, Suspense } from 'react';
const Main = lazy(()=> import('../pages/Main/Main'));
......
const SuspenseComponent = Component => props => {
return (
<Suspense fallback={null}>
<Component {...props}></Component>
</Suspense>
)
}
export default [{
component: BlankLayout,
routes:[
{
path:'/',
exact: true,
render: () => < Redirect to = { "/home" }/>,
},
{
path:'/home',
component: Tabbuttom,
routes: [
{
path: '/home',
exact: true,
render: () => < Redirect to = { "/home/main" }
/>,
},
{
path: '/home/server',
// 封装SuspenseComponent函数 动态路由, 当切换到对应路由时,才加载对应组件
component: SuspenseComponent(Server),
},
......
]
},
{
path: '/detail/:id',
component: SuspenseComponent(Detail),
}
]
}]
在配置路由的过程中使用了辰时是几点到几点lazy+Suspense
优化,所有Component数据库系统
是通过懒加载加载gitee进来的,所以渲染页面的时候可能会有延迟,但使用了Suspensgitlabe
之后,可优化交互。
使用 rende仓鼠寿命rRouter
渲染下级路由
为了使路由生效,在App.js
中需要开启子路由的地方使用 renderRoutes
import React from 'react';
// renderRoutes 读取路由配置转化为 Route 标签
import { renderRoutes } from 'react-router-config';
import { BrowserRouter } from 'react-router-dom';
// 所有组件的外壳组件
function App() {
return (
<Provider store={store}>
<div className="App">
<BrowserRouter>
{renderRoutes(routes)}
</BrowserRouter>
</div>
</Provider>
)
}
export default App;
这里使用的是browser
路由,让url里面没有#
号,相比较hash
路女配每天都在抱大腿我要成仙由美观了不少。
同时在App.js数据库系统概论第五版课后答案
中最外层中提供了sto女配满眼都是钱re
仓库,使得每一个路由都可以提取到总仓库里面的数据。
完整代码点击这里
接下来就是项目的一级路由
一级路由里面的内容在每个页面都可以看见,所以在一级路由上面写一个Tabhttp协议buttom
组件,放在页面的最下面,然后在Tabbuttom
里的liGitnk
来改变路由,显示不同的组件达到改变页面的效果,在组件里面一定要写{renderRoutes(route.routes)}
来渲染要显示的路由。以下是部分核心代码:
......
const Bottom = (props) => {
......
return (
<>
{/* fdfdasfafaafdas */}
{/* 二级路由而准备 */}
{renderRoutes(route.routes)}
<ul className="Botton-warper">
<li
onClick={() => { changeIndex(0) }}
className="Botton-warper-warp"
key="1">
<Link to="/home/main"
style={{ textDecoration: "none" }}>
<div>
<div className="icon" style={index === 0 ? { display: "none" } : {}}>
<img src={main} alt="" />
</div>
<div className="icon1" style={index === 0 ? {} : { display: "none" }} >
<img src={main} alt="" />
</div>
<div
className="planet">首页</div>
</div>
</Link>
</li>
......
</ul>
</>
)
}
数据流管理
解决完一级路由之后就可以开始写二级路由里面的组件了,这样项目的大致模样就出来了,但是有一个非常重要的东西还没有搞定,那就是数据流管理,关于数据流的管理,我觉得三元大大的小册里面写的很好,我也是根据三元大大的数据流管理来写这个项目的,[感兴趣的可以去看看这本小册]。(/book/684473…)
在数据流管女配末世养崽日常理数据库原理及应用中,有仓鼠寿命一个总仓库在store中,它可以把所有的分仓库通过redux-http://www.baidu.comthunk合并起来。
store/reducer.js
import thunk from 'redux-thunk';
import { createStore, compose, applyMiddleware, combineReducers } from 'redux';
import reducer from "./reducer";
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)));
export default store;
s辰时是几点到几点tore/index.js
import { combineReducers } from 'redux';
import { reducer as mainReducer } from '../pages/Main/store/index'
import { reducer as serverReducer } from '../pages/server/store/index'
import { reducer as myReducer } from '../pages/my/store/index'
import { reducer as infoReducer } from '../pages/info/store/index'
export default combineReducers({
main: mainReducer,
server: serverReducer,
my:myReducer,
info:infoReducer
});
建立女配末世带娃求生好redux
仓库之后,女配美炸天在provider
包裹的组件里面就要用connect
连接起两个仓库,这样才可以使用仓库里的数据。
这是其中一个页面的结构:
统一在indegiti轮胎x.js抛出文件,constants文件用来写dispatch的名字,actionCreators.js来具数据库设计体操作传来的数据,r长生十万年eduhttp://192.168.1.1登录cer.js来具体返回数据。
//reducer.js
import * as actionTypes from './constants';
const defaultstate = {
maindata: [],
num: 9,
index: 0 // tabbar 哪个被激活? 核心状态
}
const reducer = (state = defaultstate, action) => {
switch (action.type) {
case actionTypes.SET_INDEX:
return {...state, index: action.data}
case actionTypes.SET_NUM:
return {...state, num: action.data }
case actionTypes.CHANGE_MAINDATA:
return {...state, maindata: action.data }
// console.log(state,'88888888888888888888');
return state;
default:
return state;
}
}
export default reducer;
//actionCreators.js
export const getMainData = () => {
return (dispatch) => {
reqmain()
.then((res) => {
console.log(res);
dispatch(changeMainData(res.data.data))
})
.catch((e) => {
console.log('出错了');
})
}
}
连接到数据库了之后,就可以开始编写页面了,每个页面都是基于这个模板来增加功能的:
import { connect } from 'react-redux'
const Main = (props) => {
const { getMainDataDispatch } = props
const { maindata } = props
return (
<>
....
</>
)
}
const mapStateToPorps = (state) => {
return {
maindata: state.main.maindata
}
}
const mapStateToDispatch = (dispatch) => {
return {
getMainDataDispatch() {
dispatch(actionTypes.getMainData())
}
}
}
export default connect(mapStateToPorps, mapStateToDispatch)(Main)
移动端适配
使用 postcss-px女配没有求生欲txt宝书网-to-viewport 插件将px自动转为vw的移动端适配方案
npm install postcss-px-to-viewport --save-dev
- 参数配置
postcss.config.js
"postcss-px-to-viewport": {
// options
unitToConvert: "px", // 需要转换的单位
viewportWidth: 750, // 设计稿的视口宽度
unitPrecision: 5, // 单位转换后保留的精度
propList: ["*"], // 能转换的vw属性列表
viewportUnit: "vw", // 希望使用的视口单位
fontViewportUnit: "vw", // 字体使用的视口单位
selectorBlackList: [], // 需要忽略的css选择器
minPixelValue: 1, // 设置最小的转换数值,如果为1,只有大于1的值才会被转换
mediaQuery: false, // 媒体查询中是否需要转换单位
replace: true, // 是否直接更换属性值
exclude: [],
landscape: false,
landscapeUnit: "vw", // 横屏时使用的单位
landscapeWidth: 568 // 横屏时使用的视口宽度
}
这样就适配好了,可以尽情使用了。
页面开发
首先这是首页的界面:
由于这是辰时是几点到几点一个移动端项目,所以在页面中间都是有一个scroll组件让页面可以像手机一样向下数据库系统工程师滑动
而这里有三个不同种类的文章,并且文章出现的顺序是随机的,所以这就需要在后台使用mockjs生成陈思思三种不同种类的数据若干,然后再使用map循环出三种不一样的文章。
数据是一次性请求20条,当浏览到最数据库系统工程师后一条数据的时候,这时候再去向后台请求数据,就可以做到一直有数据显示出来,这就需要使用uesEffect来监听page数据,当pagNPMe的数字发生变化的时候,再次向后台请求数据,把文章显示出来。部分核心代码如下:
const Main = (props) => {
const fetchText1 = () => {
api.reqlist(page)
.then(res => {
settext1([
...text1,
...res.data.data.text1
])
})
}
return (
<>
<div className="main">
<Title />
<Scroll
ref={scrollref}
direction={"vertical"}
// refresh={false}
refresh={true}
onScroll={
(e) => {
forceCheck()
}
}
pullUp={handlePullUp}
>
<div className="main-padding" >
<SearchInput handleOnclick={() => { handleOnclick() }}
searchBoxHandleOnclick={() => history.push('/search')} />
<Swipers rotationImg={rotationImg} />
<ImgList />
<ListData text1={text1} />
</div>
</Scroll>
</div>
</>
)
}
接下来的是车友圈页面:
这里为了实现评论功能,使用了两个库,一个是momont
库,用来转化时间,可以在评论之后看到使用者在多久之前评论的,还有一个是由于LokiJS
库,LokiJS
是纯 JavaScript 实现的内存数据库,面向文档,所以可以用LokiJS
来存储评论。
const Info = (props) => {
return (
<>
<div className="main" onClick={() => { console.log('father') }}>
<Title />
<div className="main-padding" >
<div className="nav">
{
infodata.map((item, index) => {
return (
<div className="nav-items" key={item.id} onClick={() => change_id(item.id)}>
<div className="items2"></div>
</div>
)
})
}
</div>
</div>
<Commonts className='father' commontsindex={commontsindex} id={console.log('commonts的id是', commontsindex)} commonts={commontslist} />
</div>
</>
)
}
export default connect(mapStateToProps, mapStateToDispatch)(Info)
还在优化中……详见此
最后做了一个简易的登录注销界面,这就是用localstorage来存储登录信息,判断是否登录过,当存在登录状态时自动登录,就不详解了。
使用koa构建数据库查询语句后台
在构建仓鼠寿命后台的过程中,把需要的数据库技术数据用json格式传输到index.js中,使用了mockjs来模拟后女配满眼都是钱台数据,koa-cors来解决跨域问题,使用koa-static来传输静态资源。
const Koa = require('koa')
const router = require('koa-router')();
const app = new Koa()
const cors = require('koa2-cors')
const Mock = require('mockjs')
const Random = Mock.Random
app.use(require('koa-static')('./Public'));
// const querystring = require('querystring');
app.use(cors({
origin: function (ctx) { //设置允许来自指定域名请求
// if (ctx.url === '/test') {
return '*'; // 允许来自所有域名请求
// }
// return 'http://121.40.130.18:5678'; //只允许http://localhost:8080这个域名的请求
},
maxAge: 5, //指定本次预检请求的有效期,单位为秒。
credentials: true, //是否允许发送Cookie
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], //设置所允许的HTTP请求方法
allowHeaders: ['Content-Type', 'Authorization', 'Accept'], //设置服务器支持的所有头信息字段
exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'] //设置获取其他自定义字段
}))
router.get('/home/main', async (ctx) => {
ctx.response.body = {
success: true,
data: MainData
}
})
router.get('/home/list', async (ctx) => {
let {
limit = 40, page = 1
} = ctx.request.query
console.log(limit, page, "###");
console.log(ctx.request.query, "----------------");
// 根据limit 和page 做数据筛选
// 参数 page limit
let data = Mock.mock({
'text1|20': [{
'id': "@increment",
"title": "@ctitle(15,20)",
'writer': '@ctitle(3,5)',
'time': '@time(MM-dd HH:mm)',
'imgsrc1': '@img(110x80)',
'imgsrc2': '@img(110x80)',
'imgsrc3': '@img(110x80)',
'imgsrc4': '@img(110x120)',
'type|1': Random.range(1, 4, 1),
}],
})
ctx.body = {
success: true,
data
}
})
ctx.body = {
success: true,
data: data2
}
})
app
.use(router.routes())
.use(router.allowedMethods())
// 1. http服务
// 2. 简单的路由模块
// 3. cors
// 4. 返回数据
app.listen(5678, () => {
console.log('server is running in port 5678')
})
优化
使用React.me数据库管理系统mogithub是干什么的
组件是构成React视图的一个基本单元。女配美炸天有些组件会有自己本地的状态(state), 当它们的值由于用户的操作而发生改变时,组件就会重新渲染。在一个React应用中,一个组件可能会被频繁地进行渲染。这些渲染虽然有一小部分是必须的,不过大多数github永久回家地址都是无用的,它们的存在会大大降低我们应用的性能。
Reac数据库是什么t.memo会返回一个纯化的组件辰时是几点到几点MemoFuncComponent
,这个组件将会在JSX标记中渲染出来。当组件的参数props和状态数据库系统的核心是state发生改变时,React将会检查前一个状态和参数是否和下一个github状态数据库原理及应用和参数是否相同,如果相http代理同,组件将不会被渲染,如果不同,组件将会被重新渲染。
export default React.memo(Main)
图片懒加载
使用rea女配没有求生欲藤萝为枝ct-lazyload 库实现图片懒加载
import Lazyload from 'react-lazyload'
<Lazyload height={100} placeholder=
{
<img width="100%" height="100%"
src={loading}
/>
}>
<div className="ListItem-content__img1">
<img src={item.imgsrc3} alt="" />
</div>
</Lazyload>
alias
当我们的代码中出现importreact式,webpack会采取向上递归搜索的方法去node_modules目录焯是什么梗下找,为了减少搜索范围我们可以直接告诉webpack去那个路径下面找,也就是别名alias的配置
import * as api from '@/api'
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
总结
第一次写完整的r陈思思eact项目,在边写项目的时候先学习,主要还是学习react写项目的过程,学习了怎么动手,也是对自己一段时间的学习总结,逻辑方面主要是用hooks写的,项目也还在不断完善中,欢迎感兴趣的小伙伴来点评指点。
源码
-
gitee地址
-
线上地址 (移动端项目,记得使用模拟器查看。)