Skip to content

React高级 - 搭建网站

搭建网站

本文档主要从大方向规划网站的功能点,并逐个开发搭建完成。

首先,明确网站搭建需要实现的功能:

  • 1、引入功能全面的 UI 组件库
  • 2、封装: ajax 请求 / hooks / 组件 ...
  • 3、配置多环境:本地 / 测试 / 预生产 / 开发 ...
  • 4、自动化流程:测试 / 打包部署 ...
  • 5、认证方式: jwt
  • 6、其他配置:Typescript / Todo ...

然后就是针对上述功能点逐个进行实现。

项目默认基于 create-react-app 脚手架生成。

一、引入 【ant design】

js
// 1. 引入UI组件库
$ yarn add antd

// 2. 使用UI组件库
import { DatePicker } from 'antd';
import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'
ReactDOM.render(<DatePicker />, mountNode);

二、引入 【axios】

js
// 1. 引入 axios 依赖
$ yarn add axios

// 2. 配置 axios 相关文件
// utils/http 文件
// 	    配置【axios】的拦截和请求封装
// 	    前置拦截
// 	    后置拦截
// api/index 文件
//	    引入 utils/http 
//	    编写具体的api接口地址
点击查看 utils/http.js 代码
js
/**
 * 网络请求配置
 */
 import axios from "axios";
 import { API_PATH } from './config'

 let env = API_PATH ||  '';

 axios.defaults.timeout = 100000;
 axios.defaults.baseURL = env || "http://www.ycy88.com/api";
 
 /**
  * http request 拦截器
  */
 axios.interceptors.request.use(
   (config) => {
    console.log('-------------- Req 前置拦截器 ---------------------')
     config.data = JSON.stringify(config.data);
     config.headers = {
       "Content-Type": "application/json",
     };
     return config;
   },
   (error) => {
     return Promise.reject(error);
   }
 );
 
 /**
  * http response 拦截器
  */
 axios.interceptors.response.use(
   (response) => {
    console.log('-------------- Res 后置拦截器 ---------------------')
     if (response.data.errCode === 2) {
       console.log("过期");
     }
     return response;
   },
   (error) => {
    console.log('-------------- Res 后置拦截器 Error ---------------------')
     console.log("请求出错:", error);
   }
 );
 
 /**
  * 封装get方法
  * @param url  请求url
  * @param params  请求参数
  * @returns {Promise}
  */
 export function get(url: string, params = {}) {
   return new Promise((resolve, reject) => {
     axios.get(url, {
         params: params,
       }).then((response) => {
         landing(url, params, response.data);
         resolve(response.data);
       })
       .catch((error) => {
         reject(error);
       });
   });
 }
 
 /**
  * 封装post请求
  * @param url
  * @param data
  * @returns {Promise}
  */
 
 export function post(url: string, data: object) {
   return new Promise((resolve, reject) => {
     axios.post(url, data).then(
       (response) => {
         //关闭进度条
         resolve(response.data);
       },
       (err) => {
         reject(err);
       }
     );
   });
 }
 
 /**
  * 封装patch请求
  * @param url
  * @param data
  * @returns {Promise}
  */
 export function patch(url: string, data = {}) {
   return new Promise((resolve, reject) => {
     axios.patch(url, data).then(
       (response) => {
         resolve(response.data);
       },
       (err) => {
         msgFunc(err);
         reject(err);
       }
     );
   });
 }
 
 /**
  * 封装put请求
  * @param url
  * @param data
  * @returns {Promise}
  */
 
 export function put(url: string, data = {}) {
   return new Promise((resolve, reject) => {
     axios.put(url, data).then(
       (response) => {
         resolve(response.data);
       },
       (err) => {
         msgFunc(err);
         reject(err);
       }
     );
   });
 }
 
 //统一接口处理,返回数据
const HTTP =  function (fecth: string, url: string, param = {}) {
   let _data = "";
   console.log(_data)
   return new Promise((resolve, reject) => {
     switch (fecth) {
       case "get":
         console.log("begin a get request,and url:", url);
         get(url, param)
           .then(function (response) {
             resolve(response);
           })
           .catch(function (error) {
             console.log("get request GET failed.", error);
             reject(error);
           });
         break;
       case "post":
         post(url, param)
           .then(function (response) {
             resolve(response);
           })
           .catch(function (error) {
             console.log("get request POST failed.", error);
             reject(error);
           });
         break;
       default:
         break;
     }
   });
 }
 
 //失败提示
 function msgFunc(err: any) {
   if (err && err?.response) {
     switch (err.response.status) {
       case 400:
         alert(err.response.data.error.details);
         break;
       case 401:
         alert("未授权,请登录");
         break;
 
       case 403:
         alert("拒绝访问");
         break;
 
       case 404:
         alert("请求地址出错");
         break;
 
       case 408:
         alert("请求超时");
         break;
 
       case 500:
         alert("服务器内部错误");
         break;
 
       case 501:
         alert("服务未实现");
         break;
 
       case 502:
         alert("网关错误");
         break;
 
       case 503:
         alert("服务不可用");
         break;
 
       case 504:
         alert("网关超时");
         break;
 
       case 505:
         alert("HTTP版本不受支持");
         break;
       default:
     }
   }
 }
 
 /**
  * 查看返回的数据
  * @param url
  * @param params
  * @param data
  */
 function landing(url: string, params = {}, data: any) {
   if (data && data?.code === -1) {
   }
 }
 
export default HTTP;

三、引入【支持TS】

js
// 1. 安装typescript及声明类型
$ yarn add typescript @types/react @types/react-dom @types/node @types/jest

// 2. 配置tsconfig.json
// 其他: tsc --init 可以生成tsconfig.json

四、【配置多环境】

js
// 1. 安装dotenv: 
$ yarn add dotenv-cli

// 2. 根目录创建env等文件: 【.env】、【.env.dev】、【.env.test】、【.env.prod】等
REACT_APP_API_PATH = 'http://ycy88.com/dev' // 比如 .env.dev 里面配置

// 3. src/utils目录下配置 【config.ts】
export const API_PATH = process.env.REACT_APP_API_PATH;

// 4. 修改package.json配置文件
"scripts": {
    "serve": "dotenv -e .env.dev craco start",
    "dev": "dotenv -e .env.dev craco start",
    "start": "dotenv -e .env.dev craco start",
    "build": "dotenv -e .env.prod craco build",
    "build:dev": "dotenv -e .env.dev craco build",
    "build:test": "dotenv -e .env.test craco build",
    "test": "craco test",
    "eject": "craco eject"
},

五、引入【redux】

配置编写 store 相关文件

js
// 1. 安装依赖: 
$ yarn add react-redux
$ yarn add @reduxjs/toolkit
$ yarn add redux-persist    // React-redux持久化

// 2. 创建 Redux 状态(单个)
// ====== src/store/counterSlice.js
import { createSlice } from '@reduxjs/toolkit'
export const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0,
  },
  reducers: {
    increment: (state) => {
      state.value += 1
    },
    decrement: (state) => {
      state.value -= 1
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload
    },
  },
})
export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer

// 3. 将单个Redux注入全局桶
// ====== store/index.js
import { configureStore } from '@reduxjs/toolkit'
import storage from 'redux-persist/lib/storage'
import {combineReducers} from "redux"; 
import { persistReducer } from 'redux-persist'
import thunk from 'redux-thunk'
import counterReducer from './counterSlice'

const reducers = combineReducers({
  counter: counterReducer,        
});
const persistConfig = {
  key: 'root',
  storage
};
const persistedReducer = persistReducer(persistConfig, reducers);
const store = configureStore({
    reducer: persistedReducer,
    devTools: process.env.NODE_ENV !== 'production',
    middleware: [thunk]
});
export default store;

将store注入全局组件中

jsx
// ====== src/index.js             // 入口文件
import React, { Suspense } from "react";
import { createRoot } from "react-dom/client";
// React-redux
import { Provider } from 'react-redux'
import store from './store'
// 持久化:本质还是把store存到 local Storage 里面
import { PersistGate } from 'redux-persist/integration/react'
import { persistStore } from 'redux-persist'

const root = createRoot(document.getElementById("root"));
// 持久化
let persistor = persistStore(store);
root.render(
    <Provider store={store}>
        <PersistGate loading={null} persistor={persistor}>
            {/* ... */}
        </PersistGate>
    </Provider>
);

六、引入【react-router】

js
// 1. 安装依赖:
$ yarn add react-router-dom

// 2. 配置route :类似vue
// ====== src/routes/index.js
import React, { lazy } from "react";
// 懒加载路由
const Home = lazy(() => import('@/pages/Home'));
const About = lazy(() => import('@/pages/about/About'));
const routes = [
  {
    key: 'index',
    path: '/',
    element: <Home />
  },
  {
    key: 'home',
    path: '/home',
    element: <Home />
  },
  {
    key: '404',
    path: '/*',
    element: <About />
  },
];
export default routes;

// ====== src/index.js      // 完整的入口文件代码
import React, { Suspense } from "react";
import { createRoot } from "react-dom/client";
// React-redux
import { Provider } from 'react-redux'
import store from './store'
// 持久化:本质还是把store存到 local Storage 里面
import { PersistGate } from 'redux-persist/integration/react'
import { persistStore } from 'redux-persist'
// 全局样式
import '@/assets/scss/index.scss';

// React-router
import {
    BrowserRouter,
    Routes,
    Route,
} from "react-router-dom";
import routes from './routes'
// 布局 & 路由页面
import Layout from '@/layout/Layout'
// 全局loading组件
import Loading from '@/components/loading/loading';

const root = createRoot(document.getElementById("root"));
// 路由表配置
const renderRoutes = (routes) => {
    return routes.map(item => {
        if (item && item.children) {
            return (
                <Route path={item.path} element={item.element} key={item.key}>
                    { renderRoutes(item.children) }
                </Route>
            );
        } else {
            return (
                <Route path={item.path} element={item.element} key={item.key}></Route>
            );
        }
    });
};
// 持久化
let persistor = persistStore(store);
root.render(
    <Provider store={store}>
        <PersistGate loading={null} persistor={persistor}>
            <Suspense fallback={<Loading />}>
                <BrowserRouter>
                    <Routes>
                        <Route path="/" element={<Layout />}>
                            { renderRoutes(routes) }
                        </Route>
                    </Routes>
                </BrowserRouter>
            </Suspense>
        </PersistGate>
    </Provider>
);

七、引入【scss】

js
// 1. 安装依赖:
$ yarn add sass node-sass@npm:sass

// 2. 文件名调整为: 【.scss】

八、路径别名配置

js
// 1. 安装依赖:
$ yarn add @craco/craco

// 2. 根目录下 新建 craco.config.js
const path = require('path')
const pathResolve = (pathUrl) => path.join(__dirname, pathUrl)
module.exports = {
  webpack: {
    // 设置别名
    alias: {
      '@': pathResolve('src'),
      '@assets': pathResolve('src/assets'),
      '@images': pathResolve('src/assets/image'),
      '@components': pathResolve('src/components'),
      '@hooks': pathResolve('src/hooks'),
      '@pages': pathResolve('src/pages'),
      '@utils': pathResolve('src/utils')
    }
  }
}

// 3. 配置package.json -- 与“配置多环境”相结合
"scripts": {
  "serve": "dotenv -e .env.dev craco start",
  "dev": "dotenv -e .env.dev craco start",
  "start": "dotenv -e .env.dev craco start",
  "build": "dotenv -e .env.prod craco build",
  "build:dev": "dotenv -e .env.dev craco build",
  "build:test": "dotenv -e .env.test craco build",
  "test": "craco test",
  "eject": "craco eject"
},

参考:Craco

九、项目完善: todo

案例源码:
React网站(园博吧)