前言

在学习了一段时间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仓库,使得每一个路由都可以提取到总仓库里面的数据。

完整代码点击这里

接下来就是项目的一级路由

懂你还是得懂车帝(React Hooks实战开发)

一级路由里面的内容在每个页面都可以看见,所以在一级路由上面写一个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连接起两个仓库,这样才可以使用仓库里的数据。

这是其中一个页面的结构:

懂你还是得懂车帝(React Hooks实战开发)

统一在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
  1. 参数配置 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 // 横屏时使用的视口宽度
    }

这样就适配好了,可以尽情使用了。

页面开发

首先这是首页的界面:

懂你还是得懂车帝(React Hooks实战开发)

由于这是辰时是几点到几点一个移动端项目,所以在页面中间都是有一个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('commontsid是', 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地址

  • 线上地址 (移动端项目,记得使用模拟器查看。)