项目搭建
项目规范
- 文件夹、文件名称统一小写、多个单词以连接符(-)连接
- JavaScript 变量名称采用小驼峰标识,常量全部使用大写字母,组件采用大驼峰
- CSS 采用普通 CSS 和 styled-component 结合来编写(全局采用普通 CSS、局部采用 styled-component)
- 整个项目不再使用 class 组件,统一使用函数式组件,并且全面拥抱 Hooks
- 所有的函数式组件,为了避免不必要的渲染,全部使用 memo 进行包裹
- 组件内部的状态,使用 useState、useReducer 业务数据全部放在 redux 中管理
- 函数组件内部基本按照如下顺序编写代码:
- 组件内部 state 管理
- redux 的 hooks 代码
- 其他 hooks 相关代码(比如自定义 hooks)
- 其他逻辑代码
- 返回 JSX 代码
- redux 代码规范如下:
- redux 目前我们学习了两种模式,在项目实战中尽量两个都用起来,都需要掌握
- 每个模块有自己独立的 reducer 或者 slice,之后合并在一起;
- redux 中会存在共享的状态、从服务器获取到的数据状态;
- 网络请求采用 axios
- 对 axios 进行二次封装
- 所有的模块请求会放到一个请求文件中单独管理
- 项目使用 AntDesign、MUI(Material UI)
- 爱彼迎本身的设计风格更多偏向于 Material UI,但是课程中也会尽量讲到 AntDesign 的使用方法
- 项目中某些 AntDesign、MUI 中的组件会被拿过来使用
- 但是多部分组件还是自己进行编写、封装、实现
craco 配置别名
react 脚手架隐藏了 webpack 配置
- 方法一:
npm run eject
- 方法二:
craco -> create-react-app config
bash
$ npm install @craco/craco@alpha -D
$ npm install craco-less@2.1.0-alpha.0
craco.config.js
js
const path = require('path')
const CracoLessPlugin = require('craco-less')
const resolve = pathname => path.resolve(__dirname, pathname)
module.exports = {
webpack: {
alias: {
'@': resolve('src'),
components: resolve('src/components'),
utils: resolve('src/utils')
}
},
plugins: [
{
plugin: CracoLessPlugin
}
]
}
CSS 样式的重置
css
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: #484848;
text-decoration: none;
}
img {
vertical-align: top;
}
ul, li {
list-style: none;
}
Router 配置
bash
$ npm i react-router-dom
router/index.jsx
jsx
import React from 'react'
import { Navigate } from 'react-router-dom'
const Home = React.lazy(() => import('@/views/home'))
const Entire = React.lazy(() => import('@/views/entire'))
const Detail = React.lazy(() => import('@/views/detail'))
const routes = [
{
path: '/',
element: <Navigate to='home' />
},
{
path: '/home',
element: <Home />
},
{
path: '/entire',
element: <Entire />
},
{
path: '/detail',
element: <Detail />
}
]
App.jsx
jsx
import React, { memo } from 'react'
import { useRoutes } from 'react-router-dom'
import routes from './router'
const App = memo(() => {
return (
<div className='app'>
<div className='header'>header</div>
<div className='page'>{useRoutes(routes)}</div>
<div className='footer'>footer</div>
</div>
)
})
index.js
jsx
import React, { Suspense } from 'react'
import ReactDOM from 'react-dom/client'
import App from '@/App'
import { HashRouter } from 'react-router-dom'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<Suspense fallback='loading'>
<HashRouter>
<App />
</HashRouter>
</Suspense>
)
Redux 配置
bash
$ npm i @reduxjs/toolkit react-redux
index.js
jsx
import React, { Suspense } from 'react'
import ReactDOM from 'react-dom/client'
import App from '@/App'
import { HashRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import store from './store'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<Suspense fallback='loading'>
<Provider store={store}>
<HashRouter>
<App />
</HashRouter>
</Provider>
</Suspense>
)
store/index.js
js
import { configureStore } from '@reduxjs/toolkit'
import homeReducer from './modules/home'
import entireReducer from './modules/entire'
const store = configureStore({
reducer: {
home: homeReducer,
entire: entireReducer
}
})
store/modules/home.js
js
import { createSlice } from '@reduxjs/toolkit'
const homeSlice = createSlice({
name: 'home',
initialState: {
productList: []
},
reducers: {}
})
export default homeSlice.reducer
store/modules/entire.js
js
const initialState = {
currentPage: 0
}
function reducer(state = initialState, action) {
switch (action.type) {
default:
return state
}
export default reducer
Axios 配置
bash
$ npm i axios
Ui 配置
bash
$ npm install @mui/material @mui/styled-engine-sc styled-components @emotion/react @emotion/styled
bash
$ npm i normalize.css antd
$ npm i classnames
项目中的坑
useState 传入的初始值只有再组件第一次被渲染时才是有效的
解决思路:
- 数据没值的时候不进行渲染
- 使用 useEffect 监听 name 变化,变化后进行 setName,但是这样页面会渲染 3 次,不是很好