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>

四、项目效果

小视频