react-router-dom6 +react18 + mobx6 配置

官网地址:react-router

本项目使用 react18.2.0 + antd 5.1.6 + react-router-dom 6.4.3 + mobx6.7.0配置,另外还配置了git commit自动修复eslint和模拟数据服务。

一、项目目录结构

(1).husky是git hooks文件夹。

文件夹中的pre-commit文件配置了 npm run lint-staged,commmit时会自动执行 lint-staged 进行eslit自动修复。

首先安装 lint-stage,lint-staged 是文件过滤器,它只会校验你提交或者说你修改的部分内容

npm install lint-stage --save-dev

package.json中配置

scripts里面中配置

"scripts": { 
        "lint-staged": "lint-staged"
    }

再配置,lint-staged就配置完成了,到这步eslint只能手动执行命令修复,再搭配hucky可以实现提交时自动修复。

"lint-staged": {
    "**/*.{js,jsx}": [
      "eslint --fix"
    ]
  }
hucky安装配置
# husky使用(v8.0)
#### 1.npm install husky --save-dev 安装
#### 2.npx husky install  手动启用husky
#### 3.npx husky add .husky/pre-commit "npm run lint-staged"  生成husky配置文件(执行完这一步,根目录会有一个 .husky目录)

#### 4.为了避免手动启动在package.json的 scripts 里面添加如下配置

hucky下的pre-commit文件

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run lint-staged

script里面再配置 "prepare": "husky install" ,自动修复配置完成

 "scripts": { 
        "prepare": "husky install" ,
        "lint-staged": "lint-staged"
    }

 

(2)mock是模拟数据配置

mock文件夹下的server文件是express node服务器的配制,此外还应用了mock.js进行模拟数据生成,使用时进入当前文件夹执行 node server 即可开启服务。

express服务器配置

//nodejs v14版以上,默认情况下不再有require,需要手动添加以下这二行代码
/*------------*/
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
/*-----------*/

const Mock = require('mockjs');
const express = require('express');
const app = express();
const multer = require('multer');
const upload = multer(); // for parsing multipart/form-data

app.use(express.json()); // for parsing application/json
app.use(express.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded

// 解决跨域问题

app.all("*", function(req, res, next){
    res.header("Access-Control-Allow-Origin", "*");
    res.header('Access-Control-Allow-Headers', 'content-type,x-custom-header');
    next();
});

/*当是Multipart/form-data的请求头时,需要给post第二个参数加upload.array()
  实例 :
  app.post('/upload',upload.array(), function (req, res) {})
*/
app.post('/upload', upload.array(), function (req, res) {
    //post获取参数
    res.send({
        user: req.body
    });
});


app.post('/login', function (req, res) {
    console.log('res', res.query);
    //post获取参数 req.body
    res.send({
        user: req.body
    });
});

app.get('/userList', function (req, res) {
    //get获取参数 req.query
    // const params = req.query;
    //mock生成模拟数据
    const data = Mock.mock({
        "list|10": [
            {
                "id|+1": 1, //生成id,自增1
                "userName": "@cname", //生成姓名(这里生成的是中文名称)
                "userImg": "@Image('100*40','#c33','#ffffff','商品')", //生成随机图片(大小/背景色/字体颜色/文字信息),打印的是图片地址
                "userAddress": "@county(true)", //随机生成地址
                "userDate": "@date('yyyy-MM-dd')", //随机生成yyyy-MM-dd格式的日期
                "userPhone": /^1(5|3|7|8)[0-9]{9}$/, //随机生成电话号码
                "userStart|1-5": "★", //随机生成1至5个指定的图形(★)
            },
        ]
    });

    res.json({
        list: data.list
    });
});

app.listen(3000, ()=>{
    console.log("服务地址:http://localhost:3000/");
});

 

 (3)scripts是webpack5的配置文件

webpack配置

图片配置webpack5官方有了内置模块不需要在使用url-loader和file-loader了,官网地址:https://webpack.docschina.org/guides/asset-modules/#custom-output-filename

module: {
        rules: [
            //发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。
            {
                test: /\.(png|jpg|jpeg|gif)/,
                type: 'asset/resource',
                generator: {
                    filename: 'assets/images/[hash][ext][query]'
                }
            },
            // 输出data URI,类似于url-loader
            {
                test: /\.(png|jpg|jpeg|gif)/,
                type: 'asset/inline'
            },
            //webpack 将按照默认条件,自动地在 resource 和 inline 之间进行选择:小于 20kb 的文件,将会视为 inline 模块类型,否则会被视为 resource 模块类型。
            {
                test: /\.(png|jpg|jpeg|gif)/,
                type: 'asset', //
                parser: {
                    dataUrlCondition: {
                        maxSize: 20 * 1024 // 20kb
                    }
                }
            }
        ]
    },

打包之前清除之前的旧包,直接在output中配置clean: true就可以了。

output: {
        path: path.join(__dirname, '../dist'),
        filename: '[name].js',
        clean: true, // 在生成文件之前清空 output 目录  (clean-webpack-plugin插件,已经内置了,不用安装了)
    },

css压缩插件官方推荐使用 css-minimizer-webpack-plugin,js压缩 terser-webpack-plugin 插件官方已经内置了不需要自定义的话可以直接引入使用,要自定义的话仍需安装。

const TerserPlugin = require("terser-webpack-plugin"); //压缩js
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); //css抽离
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); //压缩 CSS

optimization: {
        minimizer: [new TerserPlugin(), new CssMinimizerPlugin()]
    }

二、入口文件index.js配置,react18版本发生了一些变化,改为以下写法

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './app';

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

三、Routes组件替换了v5版的Switch组件

router.js里面定义的路由

import React, { lazy } from 'react'
import { UserOutlined, VideoCameraOutlined } from '@ant-design/icons'

const Goods = lazy(() =>
  import(/* webpackChunkName: "Goods" */ 'pages/goods')
)
const List = lazy(() =>
  import(/* webpackChunkName: "List" */ 'pages/list')
)
const router = [
  {
    path: '/home/goods',
    component: Goods,
    name: '商品页',
    icon: <UserOutlined/>

  },
  {
    path: '/home/list',
    component: List,
    name: '列表页',
    icon: <VideoCameraOutlined/>
  }
]

export default router

二级路由引用到一级路由里面,路径要用一级路由的路径作为开头。默认显示的二级路由可以用以下写法。

<Route path="/home"  element={<Home/>}>
   {/* 二级路由 */}
   <Route index element={<HomeIndex/>}/>
</Route>

app.js

import React, { Suspense } from 'react';
import { HashRouter, BrowserRouter, Routes, Route } from "react-router-dom";
import { ConfigProvider } from 'antd';
import zh_CN from 'antd/lib/locale-provider/zh_CN';
import routerConfig from 'routerConfig';
import Login from 'pages/login';
import Home from 'pages/home';

import './App.less';

export default function App() {
    const HomeIndex = routerConfig[0]?.component;

    return (
        <ConfigProvider locale={zh_CN}>
            {/* BrowserRouter 对应history模式  HashRouter对应hash 模式 */}
            <HashRouter>
                {/* react-router-dom v6 使用“Routes”代替“Switch”  */}
                <Suspense fallback={<div>loading</div>}>
                    <Routes>
                        <Route path="/" element={<Login/>}/>
                        <Route path="/home" element={<Home/>}>
                            {/* 二级路由 */}
                            <Route index element={<HomeIndex/>}/>
                            {
                                routerConfig.map(item=>{
                                    return (
                                        <Route path={item.path} key={item.path} element={<item.component/>} />
                                    );
                                })
                            }
                        </Route>
                        <Route path="*" element={<Login/>}/>
                    </Routes>
                </Suspense>
            </HashRouter>
        </ConfigProvider>
    );
}

 

四、跳转方式用useNavigate方法或者使用NavLink和Link

useNavigate方法使用,只能在函数组件中使用,参数传递可以直接路径拼接,也可以设置第二个参数,获取可以使用react-router-dom提供的 useSearchParams, useLocation 来获取。

import React from 'react';
import { useNavigate, useSearchParams, useLocation } from "react-router-dom";

const Home = (props) => {
    const navigate = useNavigate();
    const navClick = () => {
        //路由跳转
        navigate('/home');
        //history 的replace模式
        // navigate('/home',{ replace: true })

        //传参
        /* navigate('/home?name=tom&age=18'); //方式一
        navigate('/Detail/Shop', { state: { name: 'tom', age: "20" }}); //方式二
        //方式一获取参数
        const [search, setsearch] = useSearchParams();
        console.log(search.get('name'));
        console.log(search.get('age'));

        //方式二获取参数,使用useLocation获取search参数
        const state = useLocation();
        console.log(state); */

    };
    return (
        <div onOnclick={navClick}>页面跳转</div>
    );
};

NavLink的使用

<NavLink to='/home'>首页</NavLink>

五、antd配置home页

react-router-dom提供了Outlet组件,它可以把二级路由的页面内容显示到这里,相当于vue的router-view

import React, { useState, Suspense, useEffect } from 'react';
import { UserOutlined, VideoCameraOutlined } from '@ant-design/icons';
import { Layout, Menu } from 'antd';
import { Routes, Route, useNavigate, Outlet, NavLink } from "react-router-dom";
import routerConfig from 'routerConfig';

import About from '../goods';
import List from '../list';

import './index.less';

const { Header, Sider, Content } = Layout;

const Home = (props) => {
    const [collapsed, setCollapsed] = useState(false);  

    const navigate = useNavigate();
    const navClick = (e) => {
        //路由跳转
        navigate(e.key)
    };
    
    return( 
    <Layout className='home-main'>
        <Sider trigger={null} collapsible collapsed={collapsed}>
            <div className="title"> 框架构建demo </div>
            <Menu
                className='menu'
                theme="dark"
                mode="inline"
                defaultSelectedKeys={['/home/goods']}
                onClick={navClick}
                items={[
                    {
                        key: '/home/goods',
                        icon: <UserOutlined />,
                        label: "商品"
                    },
                    {
                        key: '/home/list',
                        icon: <VideoCameraOutlined />,
                        label: "列表"
                    }
                ]}
            />
        </Sider>
        <Layout className="site-layout">
            <Header className="site-layout-background" style={{ padding: 0 }}></Header>
            <Content
                className="site-layout-background"
                style={{
                    margin: '24px 16px',
                    padding: 24,
                    minHeight: 280,
                }}
            >
                <Suspense fallback={<div>loading</div>}>
                    {/* Outlet相当于vue的router-view */}
                    <Outlet/>
                </Suspense>
            </Content>
        </Layout>
    </Layout>
    )
};

export default Home;

到这里基本配置就完成了。

六、mobx 6配置

mobx 6不在支持装饰器的写法需要安装 mobx 和 mobx-react-lite(此包是用来关联React与mobx的),达到数据状态管理的趋势。

npm i mobx mobx-react-lite

创建store文件,在mobx内部引入makeAutoObservable进行数据的响应式管理;

import { runInAction, makeAutoObservable } from 'mobx';
import * as api from '../../api/index';

export default class UserStore{
    constructor() {
        // 对初始化数据进行响应式处理 mobx v6不支持装饰器写法了
        makeAutoObservable(this);
    }

    loading = true;

    user = {};

    userList = [];

    //获取用户列表
    async getUserList() {
        const res = await api.getUserList();
        if(res.data){
            runInAction(()=>{
                this.userList = res.data?.list;
                this.loading = false;
            });
        }
    }

    //销毁组件时重置参数
    reset(){
        this.user = {};
        this.userList = [];
        this.loading = true;
    }
}

根store文件对store进行整合

import UserStore from './user';
class Store{
    constructor(){
        this.userStore = new UserStore();
    }
}
export default new Store();

使用先用 mobx-react 中的 Provider 对根组件进行包裹,再把定义的store透传过去

import React, { Suspense } from 'react';
import { HashRouter, Routes, Route } from "react-router-dom";
import { Provider } from 'mobx-react';
import store from './store';
import Home from 'pages/home';

export default function App() {

    return (
          <Provider {...store}>
                {/* BrowserRouter 对应history模式  HashRouter对应hash 模式 */}
                <HashRouter>
                    {/* react-router-dom v6 使用“Routes”代替“Switch”  */}
                    <Suspense fallback={<div>loading</div>}>
                        <Routes>
                            <Route path="/" element={<Login/>}/>
                            <Route path="/home" element={<Home/>}/>
                            <Route path="*" element={<Login/>}/>
                        </Routes>
                    </Suspense>
                </HashRouter>
          </Provider>
    );
}

组件中,使用 mobx-react-lite 中的 observer 对组件包裹一下进行关联

import React, { useEffect } from 'react';
import { observer } from 'mobx-react-lite'; // 从mobx-react-lite内部引入observer让mobx与react进行关联
import useRootStore from "../../store";
import { Table } from "antd";

function List (props) {
    const { userStore } = useRootStore;
    useEffect(()=>{
        userStore.getUserList();
        return ()=>{
            console.log('---组件销毁--');
            userStore.reset();
        };
    }, []);

    const handClick = ()=>{
        userStore.getUserList();
    };

    const columns = [
        {
            title: '姓名',
            dataIndex: 'userName',
            key: 'userName',
        },
        {
            title: '用户头像',
            dataIndex: 'userImg',
            key: 'userImg',
        },
        {
            title: '电话',
            dataIndex: 'userPhone',
            key: 'userPhone',
        },
        {
            title: '住址',
            dataIndex: 'userAddress',
            key: 'userAddress',
        },
    ];

    return (
        <>
            <h1 onClick={handClick}>列表页</h1>
            <Table dataSource={userStore.userList} columns={columns} rowKey={record => record.id}/></>
    );
}

export default observer(List);

页面展示

 

完整代码地址:https://github.com/mengyuxi/react-antd

posted @ 2022-11-13 17:07  雪旭  阅读(954)  评论(0编辑  收藏  举报