likes
comments
collection
share

React项目(仿airbnb)- 项目搭建

作者站长头像
站长
· 阅读数 54

项目介绍

这个项目是仿照爱彼迎(租房)官网写的一个项目,用了React18、Router、Redux、styled-component等技术实现,整个项目全面拥抱Hooks组件写法。

核心知识点

这次项目是对React知识点一个全面的练习和汇总,主要学习React的开发模式和编程思想,如React的开发流程、模式、项目架构,项目中的组件、工具的封装、抽取、复用的思想,以及学习整体代码如何组织。

  1. 对于第一个React项目,我们的核心是对前面所学知识进行练习、实战;
  2. 掌握React开发的流程、模式、项目架构,项目中会有很多组件、工具等封装、抽取、复用思想。
  3. 最重要的是学习React开发的模式和编程的思想,而不是局限于我上课期间所讲的内容,并且大部分样式和布局内容需要大家课程自行完成。
  4. 在这个项目过程中,我会尽量将之前所学的所有知识都运用起来,但是我们不会为了用某个知识而用某个知识。
  5. 课程中会使用我服务器已经获取到的数据,一是国内的数据更好看,二是担心它数据有一天不再维护,三是我对数据已经进行了大量的整理。
  6. 后续我们还会专门学习React+TypeScript项目实战的内容,React本身非常的灵活,对JavaScript本身要求也较高,但是最重要的还是练习。

项目规范

项目中的开发规范和代码风格如下:

  1. 文件夹、文件名称统一小写、多个单词以连接符(-)连接。
  2. JavaScript变量名称采用小驼峰标识,常量全部使用大写字母,组件采用大驼峰。
  3. CSS采用普通CSS和styled-component结合来编写(全局采用普通CSS、局部采用styled-component)。
  4. 整个项目不再使用class组件,统一使用函数式组件,并且全面拥抱Hooks。
  5. 所有的函数式组件,为了避免不必要的渲染,全部使用memo进行包裹。
  6. 组件内部的状态,使用useState、useReducer;业务数据全部放在redux中管理。
  7. 函数组件内部基本按照如下顺序编写代码:

· 组件内部state管理; · redux的hooks代码; · 其他hooks相关代码(比如自定义hooks); · 其他逻辑代码; · 返回JSX代码。

  1. redux代码规范如下:

· redux目前我们学习了两种模式(普通和RTK),在项目实战中尽量两个都用起来,都需要掌握; · 每个模块有自己独立的reducer或者slice,之后合并在一起; · redux中会存在共享的状态、从服务器获取到的数据状态;

  1. 网络请求采用axios

· 对axios进行二次封装; · 所有的模块请求会放到一个请求文件中单独管理。

  1. 项目使用AntDesign、MUI(Material UI)

· 爱彼迎本身的设计风格更多偏向于Material UI,但是课程中也会尽量讲到AntDesign的使用方法; · 项目中某些AntDesign、MUI中的组件会被拿过来使用; · 但是多部分组件还是自己进行编写、封装、实现。 其他规范在项目中根据实际情况决定和编写。

创建和配置

通过 create-react-app name创建项目。 项目配置:

  • 配置项目的icon
  • 配置项目的title
  • 配置jsconfig.json(vscode代码提示)

通过craco配置别名和less文件: craco部分:

  1. 下载craco
  2. 创建craco.config.js
  3. 更新scripts中的script

React项目(仿airbnb)- 项目搭建 别名配置部分:

  • 引入node里的path模块来获取当前文件路径,然后通过path中的resolve方法拼接路径

less配置部分:

  • 下载less:npm i craco-less
  • 通过plugins安装less库

config中的具体代码如下:

// craco.config.js
const path = require('path');
const cracoLessPlugin = require('craco-less')

const resolve = pathname => path.resolve(__dirname, pathname)

module.exports = {
  // plugins安装less
  plugins: [
    {
      plugin: cracoLessPlugin
    }
  ],

  // webpack
  webpack: {
    alias: {
      "@": resolve("src"),
      "components": resolve("src/components"),
      "utils": resolve("src/utils")
    }
  }
}

重置样式

通过normalize.css库以及自己手动在reset.css中重置样式,在variable中可以定制一些主题色变量,然后在index.css导入reset.css让index.css作为样式入口。 normalize.css:

  • npm i normalize.css
  • 在index.js中引入

reset.css:

@import './variables.less';

body, button, dd, dl, dt, form, h1, h2, h3, h4, h5, h6, hr, input, li, ol, p, pre, td, textarea, th, ul {
	padding: 0;
	margin: 0;
}

a {
	color: @textColor;
	text-decoration: none;
}

img {
	vertical-align: top;
}

ul, li {
	list-style: none;
}

variable.less:

/* less -> @ ; scss -> $ */
@textColor: #484848;
@textColorSecondary: #222;

index.less: 全局使用:在index.js中引入

@import './reset.less';

配置Router

router下载

需要下载router:npm i react-router-dom。 在index.js中引入 HashRouter或者BrowserRouter组件包裹 。

import React, { Suspense } from 'react';
import ReactDOM from 'react-dom/client';
import { HashRouter } from 'react-router-dom';

import App from './App';
import 'normalize.css'
import './assets/css/index.less'


const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
	<React.StrictMode>
		<Suspense fallback='loading...'>
			<HashRouter>
				<App />
			</HashRouter>
		</Suspense>
	</React.StrictMode>
);

划分页面/配置路由

  • 在app文件的jsx中划分页面结构,如header、main、footer等
  • 在router的routes文件中配置路由映射
  • 在app文件中需要路由的部分通过useRoutes hook传入映射
import React, { memo } from 'react'
import { useRoutes } from 'react-router-dom'

import routes from './router/routes'

const App = memo(() => {
	return (
		<div className='app'>
			<div className="header">header</div>
			<div className="page">
				{useRoutes(routes)}
			</div>
			<div className="footer">footer</div>
		</div>
	)
})

export default App

组件的异步加载

组件的异步加载(也称为代码分割)是 Webpack、Rollup 或其他模块打包工具的功能,这些工具允许你将你的应用分割成多个包,并在需要时动态加载它们。这样做的主要目的是优化应用的加载时间和性能,特别是当应用变得非常大时。

  • 更快的加载时间:通过只加载用户首次访问时所需的代码,可以减少应用的初始化时间。
  • 按需加载:用户只下载实际需要查看或交互的页面和组件的代码。
  • 缓存优化:浏览器可以缓存已下载的包,用户再次访问时可以更快的加载已下载的部分。

React Router 提供了几种方式来结合使用异步加载的组件。最常见的方法是使用 React 的 React.lazy() 和 Suspense 组件。

  • React.lazy():允许你定义一个动态导入的组件。这个组件将自动处理模块的加载。
  • Suspense:是一个可以让你在组件树中“等待”某些异步操作完成的组件,如加载组件。它可以用来在加载时显示一个备用的 UI(如加载指示器)。
  • 为什么需要用Suspense包裹:

Suspense 组件提供了一个“等待”的边界,告诉 React 在这个边界内的组件树中,如果某个组件正在加载,应该如何处理。 将 Suspense 包裹在 App 组件(或任何父组件)周围,是为了在应用级别提供一个统一的加载指示器处理机制,确保所有懒加载的组件都能以一致的方式处理加载状态。

配置映射

navigater组件用来重定向,映射对象包含path、element、children属性

import React from "react"
import { Navigate } from "react-router-dom"

const Home = React.lazy(() => import('@/views/home'))
const Detail = React.lazy(() => import('@/views/detail'))
const Entire = React.lazy(() => import('@/views/entire'))


const routes = [
	{
		path: '/',
		element: <Navigate to='/home' />
			},
	{
		path: '/home',
		element: <Home/>
			},
	{
		path: '/detail',
		element: <Detail/>
			},
	{
		path: '/entire',
		element: <Entire/>
			},
]

export default routes

配置Redux

1 .下载redux/tookit:npm i @redux/tookit react-redux 2 .配置store:configureStore

import { configureStore } from "@reduxjs/toolkit"

import homeReducer from "./modules/home"
import entireReducer from "./modules/entire"

const store = configureStore({
  reducer: {
    home: homeReducer,
    entire: entireReducer
  }
})

export default store

3 .在index.js入口文件中引入:Provider store=store

import React, { Suspense } from 'react'
import ReactDOM from 'react-dom/client'
import { HashRouter } from 'react-router-dom'
import { Provider } from 'react-redux'

import App from './App'
import 'normalize.css'
import './assets/css/index.less'
import store from './store'


const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Suspense fallback='loading...'>
      <Provider store={store}>
        <HashRouter>
          <App />
        </HashRouter>
      </Provider>
    </Suspense>
  </React.StrictMode>
)

4 .单文件store配置 toolkit方式:createSlice

// home.js

import { createSlice } from "@reduxjs/toolkit"

const homeSlice = createSlice({
  name: 'home',
  initialState: {
    productList: []
  },
  reducers: {

  }
})

export default homeSlice.reducer

普通方式:创建4个文件

  • constants.js:存放用的常量
  • createActions.js:存放用的action
  • index.js:统一出口
  • reducer.js:reducer配置
// index.js
import reducer from './reducer'

export default reducer
// reducer.js
const initialState = {
  currentPage: 3
}

function reducer(state = initialState, action) {
  switch (action.type) {
    default:
      return state
  }
}

export default reducer

配置axios

下载axios:npm i axios 在request的index.js中封装代码,在request的config中配置baseURL和timeout,然后在最外层的index.js中统一导出。 config:

const BASE_URL = ''
const TIMEOUT = 10000

export {
  BASE_URL,
  TIMEOUT
}

request -> index.js

import axios from "axios"
import { BASE_URL, TIMEOUT } from "./config"

class DuRequest {
  constructor(baseURL, timeout = 10000) {
    this.instance = axios.create({ baseURL, timeout })
  
    this.instance.interceptors.response.use((res) => {
      return res.data
    }, err => {
      return err
    })
  }

  request(config) {
    return this.instance.request(config)
  }

  get(config) {
    return this.request({ ...config, method: 'GET' })
  }

  post(config) {
    return this.request({ ...config, method: 'POST' })
  }
}

export default new DuRequest(BASE_URL, TIMEOUT)

最外层的index.js

import DuRequest from './request'

export default DuRequest

使用案例:

import React, { memo, useState, useEffect} from 'react'
import DuRequest from '@/services'

const Home = memo(() => {
  // 定义状态
  const [highScore, setHighScore] = useState([])

  // 网络请求的代码
  useEffect(() => {
    DuRequest.get({ url: '/home/highscore' }).then(res => {
      console.log(res)
      setHighScore(res)
    })
  }, [])

  return (
    <div>
      <h2>{highScore.title}</h2>
      <h4>{highScore.subtitle}</h4>
      <ul>
        {highScore.list?.map(item => {
          return <li key={item.id}>{item.name}</li>
        })}
      </ul>
    </div>
  )
})

export default Home
转载自:https://juejin.cn/post/7393644512005210122
评论
请登录