React 之 airbnb - 项目实战
一、开发前言
1. 规范
2. 创建项目
node -v => 18.0.0
npm -v => 8.6.0
create-react-app star-airbnb
3. 项目基本配置
配置jsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"jsx": "preserve",
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}
通过craco配置
react脚手架隐藏webpack
解决一 : npm run eject
导出webpack配置,要去找到对应的配置,如果修改错误,项目可能跑不起来
解决二 : 通过craco => create-react-app config
配置后,会与原来的webpack配置混合
npm install @craco/craco@alpha -D => "react-scripts": "5.0.1"
新建 craco.config.js文件
/* package.json */
"scripts": {
- "start": "react-scripts start",
- "build": "react-scripts build",
- "test": "react-scripts test",
+ "start": "craco start",
+ "build": "craco build",
+ "test": "craco test",
}
别名
const path = require('path');
const resolve = (pathName) => path.resolve(__dirname, pathName);
module.exports = {
// webpack
webpack: {
alias: {
'@': resolve('src'),
'assets': resolve('src/assets'),
'components': resolve('src/components'),
'view': resolve('src/view'),
'store': resolve('src/store'),
'utils': resolve('src/utils'),
'router': resolve('src/router'),
'services': resolve('src/services'),
'baseUi': resolve('src/base-ui')
}
}
};
less文件
可查看 Ant Design 这里所用是4点多的版本
npm i craco-less@2.1.0-alpha.0
const path = require('path');
const resolve = (pathName) => path.resolve(__dirname, pathName);
const CracoLessPlugin = require('craco-less');
module.exports = {
// less
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
// modifyVars: { '@primary-color': '#1DA57A' },
javascriptEnabled: true
}
}
}
}
],
// webpack
webpack: {
alias: {
'@': resolve('src'),
assets: resolve('src/assets'),
components: resolve('src/components'),
view: resolve('src/view'),
store: resolve('src/store'),
utils: resolve('src/utils'),
router: resolve('src/router'),
services: resolve('src/services'),
baseUi: resolve('src/base-ui')
}
}
};
css样式重置
对默认CSS样式进行重置
normalize.css
npm install normalize.css
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import 'normalize.css';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
reset.css
@mainColor: #484848;
blockquote, body, button, dd, dl, dt, fieldset, form, h1, h2, h3, h4, h5, h6, hr, input, legend, li, ol, p, pre, td, textarea, th, ul {
// color: @mainColor;
padding: 0;
margin: 0;
}
a {
color: @mainColor;
text-decoration: none;
}
img {
vertical-align: top;
}
body {
font-size: 14px;
font-family: Circular, "PingFang-SC", "Hiragino Sans GB", "微软雅黑", "Microsoft YaHei", "Heiti SC" ;
-webkit-font-smoothing: antialiased;
}
目录结构划分
4. 主题配置
项目使用styled-components,可用于配置主题
配置
theme/index.js
export const theme = {
color: {
$primaryColor: '#FF385C',
$secondaryColor: '#00848A',
$textColor: '#484848',
$textColorSecondary: '#222222'
},
fontSize: {
$small: '12px',
$normal: '14px',
$large: '16px'
},
mixin: {
$boxShadow: `
transition: box-shadow 0.2s ease;
&:hover {
box-shadow: 0 2px 4px rgba(0,0,0,0.18);
}
`
}
};
export default theme;
index.js
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom/client';
import { HashRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
// 1. 引入 Provider
import { ThemeProvider } from 'styled-components';
import App from './App';
import store from './store';
import 'normalize.css';
import 'assets/css/index.less';
import { theme } from './theme';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
{/* 2. 使用主题 */}
<ThemeProvider theme={theme}>
<HashRouter>
<Suspense fallback={<div>Loading...</div>}>
<App />
</Suspense>
</HashRouter>
</ThemeProvider>
</Provider>
);
组件使用
import styled from 'styled-components';
export const LeftWrapper = styled.div.attrs((props) => ({
// 1. 使用主题, 通过 props.theme 获取主题
$primaryColor: props.theme.color.$primaryColor,
$secondaryColor: props.theme.color.$secondaryColor
}))`
flex: 1;
display: flex;
align-items: center;
/* 2. 使用,这里是用一个回调函数 */
color: ${({ $primaryColor }) => $primaryColor};
.log {
cursor: pointer;
}
`;
5. 路由配置
npm install react-router-dom
router/index.js
import React, { lazy } from 'react';
import { Navigate } from 'react-router-dom';
const Home = lazy(() => import('view/home'));
const Entire = lazy(() => import('view/entire'));
const Detail = lazy(() => import('view/detail'));
const routes = [
{
path: '/',
element: <Navigate to='/home' />
},
{
path: '/home',
element: <Home />
},
{
path: '/entire',
element: <Entire />
},
{
path: '/detail',
element: <Detail />
}
];
export default routes;
index.js
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>
<HashRouter>
<Suspense fallback={<div>Loading...</div>}>
<App />
</Suspense>
</HashRouter>
</React.StrictMode>
);
App.jsx
import React, { memo } from 'react';
import { useRoutes } from 'react-router-dom';
import routes from 'router';
const App = memo(() => {
return (
<>
<div className='header'>header</div>
<div className='main-contain'>{useRoutes(routes)}</div>
<div className='footer'>footer</div>
</>
);
});
export default App;
6. redux状态管理配置
npm install @reduxjs/toolkit react-redux
store
modules
home.js
home.js => 使用rtk模式
import { createSlice } from '@reduxjs/toolkit';
const homeSlice = createSlice({
name: 'home',
initialState: {
a: [1, 2, 3, 4]
},
reducers: {}
});
export default homeSlice.reducer;
entire文件夹
entire => 使用原生模式,所以有四个文件
reducer.js
const initialState = {
b: [1, 2, 3, 4]
};
const reducer = (state = initialState, action) => {
switch (action.type) {
// case 'ADD_USER':
// return {
// ...state,
// [action.payload.id]: action.payload
// };
default:
return state;
}
};
export default reducer;
index.js
import reducer from './reducer';
export default reducer;
index.js
import { configureStore } from '@reduxjs/toolkit';
import homeReducer from './modules/home';
// 直接使用原生的entire,也是一样的,因为createSlice就是对原生的一种封装而已
import entireReducer from './modules/entire';
const store = configureStore({
reducer: {
home: homeReducer,
entire: entireReducer
}
});
export default store;
index.js
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 store from './store';
import 'normalize.css';
import 'assets/css/index.less';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<HashRouter>
<Suspense fallback={<div>Loading...</div>}>
<App />
</Suspense>
</HashRouter>
</Provider>
</React.StrictMode>
);
7. 网络请求 - axios配置
npm install axios
requset
config.js
export const BASE_URL = 'http://xxxxxx';
export const TIME_OUT = 20000;
index.js
import axios from 'axios';
import { BASE_URL, TIME_OUT } from './config';
class StarRequest {
constructor(baseURL, timeout) {
this.instance = axios.create({
baseURL,
timeout
});
this.instance.interceptors.response.use(
(res) => {
return res.data;
},
(err) => {
return err;
}
);
}
request(config) {
return this.instance(config);
}
get(config) {
return this.request({ ...config, method: 'get' });
}
post(config) {
return this.request({ ...config, method: 'post' });
}
}
const starRequest = new StarRequest(BASE_URL, TIME_OUT);
export default starRequest;
index.js
import starRequest from './request';
export default starRequest;
简单使用
import React, { memo, useEffect, useState } from 'react';
import starRequest from '@/services';
const Home = memo(() => {
// 定义状态
const [highscore, sethighscore] = useState({});
// 请求
useEffect(() => {
starRequest.get({ url: '/home/highscore' }).then((res) => {
console.log(res);
sethighscore(res);
});
}, []);
return (
<>
<div>Home</div>
<div>{highscore.title}</div>
</>
);
});
export default Home;
二、注意事项
图片问题
导入需使用import | require 导入方可使用
不论是背景图片还是img使用图片
import导入
import styled from 'styled-components';
import coverPic from '@/assets/img/cover_01.jpeg';
export const BannerWrapper = styled.div`
height: 529px;
background: url(${coverPic}) center center/cover;
`;
require导入
import styled from 'styled-components';
export const BannerWrapper = styled.div`
height: 529px;
background: url(${require('@/assets/img/cover_01.jpeg')}) center center/cover;
/* 根据webpack版本不同,可能需要加default */
/* background: url(${require('@/assets/img/cover_01.jpeg').default}) center center/cover; */
`;
三、引入组件库
Material-Ui
安装
npm install @mui/material @mui/styled-engine-sc
配置
const path = require('path');
const resolve = (pathName) => path.resolve(__dirname, pathName);
const CracoLessPlugin = require('craco-less');
module.exports = {
// less
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
// modifyVars: { '@primary-color': '#1DA57A' },
javascriptEnabled: true
}
}
}
}
],
// webpack
webpack: {
alias: {
'@': resolve('src'),
assets: resolve('src/assets'),
components: resolve('src/components'),
view: resolve('src/view'),
store: resolve('src/store'),
utils: resolve('src/utils'),
router: resolve('src/router'),
services: resolve('src/services'),
baseUi: resolve('src/base-ui'),
// ++++++++++++++++
'@mui/styled-engine': '@mui/styled-engine-sc'
}
}
};
Ant-Design
安装
npm install antd
使用
// 直接组件中使用即可
import { Button } from 'antd';
<Button type='primary'>Button</Button>
四、项目效果
小视频